uploadcare-ruby 4.4.2 → 5.0.0.rc1
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/.env.example +7 -0
- data/.github/workflows/gem-push.yml +1 -1
- data/.github/workflows/ruby.yml +10 -13
- data/.gitignore +9 -0
- data/.rubocop.yml +95 -8
- data/CHANGELOG.md +78 -0
- data/Gemfile +23 -6
- data/MIGRATING_V5.md +290 -0
- data/README.md +422 -671
- data/Rakefile +5 -1
- data/api_examples/README.md +77 -0
- data/api_examples/rest_api/delete_files_storage.rb +3 -5
- data/api_examples/rest_api/delete_files_uuid_metadata_key.rb +3 -4
- data/api_examples/rest_api/delete_files_uuid_storage.rb +3 -4
- data/api_examples/rest_api/delete_groups_uuid.rb +3 -4
- data/api_examples/rest_api/delete_webhooks_unsubscribe.rb +3 -4
- data/api_examples/rest_api/get_addons_aws_rekognition_detect_labels_execute_status.rb +3 -6
- data/api_examples/rest_api/get_addons_aws_rekognition_detect_moderation_labels_execute_status.rb +3 -6
- data/api_examples/rest_api/get_addons_remove_bg_execute_status.rb +3 -6
- data/api_examples/rest_api/get_addons_uc_clamav_virus_scan_execute_status.rb +3 -6
- data/api_examples/rest_api/get_convert_document_status_token.rb +3 -5
- data/api_examples/rest_api/get_convert_document_uuid.rb +3 -5
- data/api_examples/rest_api/get_convert_video_status_token.rb +3 -5
- data/api_examples/rest_api/get_files.rb +3 -5
- data/api_examples/rest_api/get_files_uuid.rb +3 -5
- data/api_examples/rest_api/get_files_uuid_metadata.rb +3 -5
- data/api_examples/rest_api/get_files_uuid_metadata_key.rb +3 -5
- data/api_examples/rest_api/get_groups.rb +3 -5
- data/api_examples/rest_api/get_groups_uuid.rb +3 -5
- data/api_examples/rest_api/get_project.rb +3 -5
- data/api_examples/rest_api/get_webhooks.rb +3 -5
- data/api_examples/rest_api/post_addons_aws_rekognition_detect_labels_execute.rb +3 -5
- data/api_examples/rest_api/post_addons_aws_rekognition_detect_moderation_labels_execute.rb +3 -5
- data/api_examples/rest_api/post_addons_remove_bg_execute.rb +3 -5
- data/api_examples/rest_api/post_addons_uc_clamav_virus_scan_execute.rb +3 -5
- data/api_examples/rest_api/post_convert_document.rb +3 -6
- data/api_examples/rest_api/post_convert_video.rb +3 -10
- data/api_examples/rest_api/post_files_local_copy.rb +3 -6
- data/api_examples/rest_api/post_files_remote_copy.rb +3 -7
- data/api_examples/rest_api/post_webhooks.rb +3 -9
- data/api_examples/rest_api/put_files_storage.rb +3 -8
- data/api_examples/rest_api/put_files_uuid_metadata_key.rb +3 -7
- data/api_examples/rest_api/put_files_uuid_storage.rb +3 -5
- data/api_examples/rest_api/put_webhooks_id.rb +3 -11
- data/api_examples/support/example_helper.rb +250 -0
- data/api_examples/support/run_rest_example.rb +161 -0
- data/api_examples/support/run_upload_example.rb +88 -0
- data/api_examples/upload_api/get_from_url_status.rb +3 -5
- data/api_examples/upload_api/get_group_info.rb +3 -6
- data/api_examples/upload_api/get_info.rb +3 -6
- data/api_examples/upload_api/post_base.rb +3 -5
- data/api_examples/upload_api/post_from_url.rb +3 -5
- data/api_examples/upload_api/post_group.rb +3 -8
- data/api_examples/upload_api/post_multipart_complete.rb +3 -7
- data/api_examples/upload_api/post_multipart_start.rb +3 -7
- data/api_examples/upload_api/put_multipart_part.rb +4 -0
- data/bin/console +1 -1
- data/docs/release-notes-5.0.0.rc1.md +34 -0
- data/examples/README.md +39 -0
- data/examples/batch_upload.rb +54 -0
- data/examples/group_creation.rb +88 -0
- data/examples/large_file_upload.rb +88 -0
- data/examples/simple_upload.rb +39 -0
- data/examples/upload_with_progress.rb +84 -0
- data/examples/url_upload.rb +56 -0
- data/lib/uploadcare/api/rest/addons.rb +107 -0
- data/lib/uploadcare/api/rest/document_conversions.rb +65 -0
- data/lib/uploadcare/api/rest/file_metadata.rb +71 -0
- data/lib/uploadcare/api/rest/files.rb +112 -0
- data/lib/uploadcare/api/rest/groups.rb +49 -0
- data/lib/uploadcare/api/rest/project.rb +23 -0
- data/lib/uploadcare/api/rest/video_conversions.rb +52 -0
- data/lib/uploadcare/api/rest/webhooks.rb +74 -0
- data/lib/uploadcare/api/rest.rb +254 -0
- data/lib/uploadcare/api/upload/files.rb +313 -0
- data/lib/uploadcare/api/upload/groups.rb +72 -0
- data/lib/uploadcare/api/upload.rb +272 -0
- data/lib/uploadcare/client/addons_accessor.rb +85 -0
- data/lib/uploadcare/client/api.rb +33 -0
- data/lib/uploadcare/client/conversions_accessor.rb +33 -0
- data/lib/uploadcare/client/document_conversions_accessor.rb +41 -0
- data/lib/uploadcare/client/file_metadata_accessor.rb +46 -0
- data/lib/uploadcare/client/files_accessor.rb +82 -0
- data/lib/uploadcare/client/groups_accessor.rb +35 -0
- data/lib/uploadcare/client/project_accessor.rb +17 -0
- data/lib/uploadcare/client/video_conversions_accessor.rb +33 -0
- data/lib/uploadcare/client/webhooks_accessor.rb +42 -0
- data/lib/uploadcare/client.rb +127 -0
- data/lib/uploadcare/cname_generator.rb +68 -0
- data/lib/uploadcare/collections/batch_result.rb +35 -0
- data/lib/uploadcare/collections/paginated.rb +165 -0
- data/lib/uploadcare/configuration.rb +81 -0
- data/lib/uploadcare/exception/auth_error.rb +2 -6
- data/lib/uploadcare/exception/configuration_error.rb +4 -0
- data/lib/uploadcare/exception/conversion_error.rb +2 -6
- data/lib/uploadcare/exception/invalid_request_error.rb +4 -0
- data/lib/uploadcare/exception/multipart_upload_error.rb +4 -0
- data/lib/uploadcare/exception/not_found_error.rb +4 -0
- data/lib/uploadcare/exception/request_error.rb +2 -6
- data/lib/uploadcare/exception/retry_error.rb +2 -6
- data/lib/uploadcare/exception/throttle_error.rb +7 -11
- data/lib/uploadcare/exception/unknown_status_error.rb +4 -0
- data/lib/uploadcare/exception/upload_error.rb +4 -0
- data/lib/uploadcare/exception/upload_timeout_error.rb +4 -0
- data/lib/uploadcare/internal/authenticator.rb +101 -0
- data/lib/uploadcare/internal/error_handler.rb +102 -0
- data/lib/uploadcare/internal/signature_generator.rb +31 -0
- data/lib/uploadcare/internal/throttle_handler.rb +36 -0
- data/lib/uploadcare/internal/upload_io.rb +110 -0
- data/lib/uploadcare/internal/upload_params_generator.rb +86 -0
- data/lib/uploadcare/internal/user_agent.rb +22 -0
- data/lib/uploadcare/operations/multipart_upload.rb +213 -0
- data/lib/uploadcare/operations/upload_router.rb +162 -0
- data/lib/uploadcare/resources/addon_execution.rb +97 -0
- data/lib/uploadcare/resources/base_resource.rb +61 -0
- data/lib/uploadcare/resources/document_conversion.rb +81 -0
- data/lib/uploadcare/resources/file.rb +366 -0
- data/lib/uploadcare/resources/file_metadata.rb +135 -0
- data/lib/uploadcare/resources/group.rb +142 -0
- data/lib/uploadcare/resources/project.rb +26 -0
- data/lib/uploadcare/resources/video_conversion.rb +59 -0
- data/lib/uploadcare/resources/webhook.rb +85 -0
- data/lib/uploadcare/result.rb +85 -0
- data/lib/uploadcare/signed_url_generators/akamai_generator.rb +60 -56
- data/lib/uploadcare/signed_url_generators/base_generator.rb +15 -15
- data/lib/uploadcare/version.rb +7 -0
- data/lib/uploadcare/webhook_signature_verifier.rb +60 -0
- data/lib/uploadcare.rb +84 -50
- data/mise.toml +2 -0
- data/uploadcare-ruby.gemspec +8 -7
- metadata +102 -74
- data/api_examples/upload_api/put_presigned_url_x.rb +0 -8
- data/lib/uploadcare/api/api.rb +0 -25
- data/lib/uploadcare/client/addons_client.rb +0 -69
- data/lib/uploadcare/client/conversion/base_conversion_client.rb +0 -59
- data/lib/uploadcare/client/conversion/document_conversion_client.rb +0 -45
- data/lib/uploadcare/client/conversion/video_conversion_client.rb +0 -46
- data/lib/uploadcare/client/file_client.rb +0 -48
- data/lib/uploadcare/client/file_list_client.rb +0 -46
- data/lib/uploadcare/client/file_metadata_client.rb +0 -36
- data/lib/uploadcare/client/group_client.rb +0 -45
- data/lib/uploadcare/client/multipart_upload/chunks_client.rb +0 -58
- data/lib/uploadcare/client/multipart_upload_client.rb +0 -64
- data/lib/uploadcare/client/project_client.rb +0 -20
- data/lib/uploadcare/client/rest_client.rb +0 -77
- data/lib/uploadcare/client/rest_group_client.rb +0 -43
- data/lib/uploadcare/client/upload_client.rb +0 -46
- data/lib/uploadcare/client/uploader_client.rb +0 -128
- data/lib/uploadcare/client/webhook_client.rb +0 -49
- data/lib/uploadcare/concern/error_handler.rb +0 -54
- data/lib/uploadcare/concern/throttle_handler.rb +0 -25
- data/lib/uploadcare/concern/upload_error_handler.rb +0 -32
- data/lib/uploadcare/entity/addons.rb +0 -14
- data/lib/uploadcare/entity/conversion/base_converter.rb +0 -43
- data/lib/uploadcare/entity/conversion/document_converter.rb +0 -15
- data/lib/uploadcare/entity/conversion/video_converter.rb +0 -15
- data/lib/uploadcare/entity/decorator/paginator.rb +0 -79
- data/lib/uploadcare/entity/entity.rb +0 -18
- data/lib/uploadcare/entity/file.rb +0 -103
- data/lib/uploadcare/entity/file_list.rb +0 -32
- data/lib/uploadcare/entity/file_metadata.rb +0 -30
- data/lib/uploadcare/entity/group.rb +0 -49
- data/lib/uploadcare/entity/group_list.rb +0 -24
- data/lib/uploadcare/entity/project.rb +0 -13
- data/lib/uploadcare/entity/uploader.rb +0 -93
- data/lib/uploadcare/entity/webhook.rb +0 -14
- data/lib/uploadcare/param/authentication_header.rb +0 -37
- data/lib/uploadcare/param/conversion/document/processing_job_url_builder.rb +0 -39
- data/lib/uploadcare/param/conversion/video/processing_job_url_builder.rb +0 -64
- data/lib/uploadcare/param/param.rb +0 -10
- data/lib/uploadcare/param/secure_auth_header.rb +0 -51
- data/lib/uploadcare/param/simple_auth_header.rb +0 -14
- data/lib/uploadcare/param/upload/signature_generator.rb +0 -24
- data/lib/uploadcare/param/upload/upload_params_generator.rb +0 -41
- data/lib/uploadcare/param/user_agent.rb +0 -21
- data/lib/uploadcare/param/webhook_signature_verifier.rb +0 -23
- data/lib/uploadcare/ruby/version.rb +0 -5
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
# Group resource representing a collection of files in Uploadcare.
|
|
6
|
+
#
|
|
7
|
+
# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Group
|
|
8
|
+
class Uploadcare::Resources::Group < Uploadcare::Resources::BaseResource
|
|
9
|
+
# API fields assigned onto group resources.
|
|
10
|
+
ATTRIBUTES = %i[
|
|
11
|
+
id datetime_removed datetime_stored datetime_uploaded is_image is_ready mime_type original_file_url cdn_url
|
|
12
|
+
original_filename size url uuid variations content_info metadata appdata source datetime_created files_count
|
|
13
|
+
files
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
attr_writer :id, :cdn_url
|
|
17
|
+
attr_accessor :datetime_removed, :datetime_stored, :datetime_uploaded, :is_image, :is_ready, :mime_type,
|
|
18
|
+
:original_file_url, :original_filename, :size, :url, :uuid, :variations,
|
|
19
|
+
:content_info, :metadata, :appdata, :source, :datetime_created, :files_count, :files
|
|
20
|
+
|
|
21
|
+
# --- Class methods ---
|
|
22
|
+
|
|
23
|
+
# List groups with optional filtering and pagination.
|
|
24
|
+
#
|
|
25
|
+
# @param params [Hash] Query parameters
|
|
26
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
27
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
28
|
+
# @param request_options [Hash] Request options
|
|
29
|
+
# @return [Uploadcare::Collections::Paginated]
|
|
30
|
+
def self.list(params: {}, client: nil, config: Uploadcare.configuration, request_options: {})
|
|
31
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
32
|
+
response = Uploadcare::Result.unwrap(
|
|
33
|
+
resolved_client.api.rest.groups.list(params: params, request_options: request_options)
|
|
34
|
+
)
|
|
35
|
+
groups = response['results'].map { |data| new(data, resolved_client) }
|
|
36
|
+
|
|
37
|
+
Uploadcare::Collections::Paginated.new(
|
|
38
|
+
resources: groups,
|
|
39
|
+
next_page: response['next'],
|
|
40
|
+
previous_page: response['previous'],
|
|
41
|
+
per_page: response['per_page'],
|
|
42
|
+
total: response['total'],
|
|
43
|
+
api_client: resolved_client.api.rest.groups,
|
|
44
|
+
resource_class: self,
|
|
45
|
+
client: resolved_client,
|
|
46
|
+
request_options: request_options
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Find a group by ID.
|
|
51
|
+
#
|
|
52
|
+
# @param group_id [String] Group UUID (formatted as UUID~size)
|
|
53
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
54
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
55
|
+
# @param request_options [Hash] Request options
|
|
56
|
+
# @return [Uploadcare::Resources::Group]
|
|
57
|
+
def self.find(group_id:, client: nil, config: Uploadcare.configuration, request_options: {})
|
|
58
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
59
|
+
response = Uploadcare::Result.unwrap(
|
|
60
|
+
resolved_client.api.rest.groups.info(uuid: group_id, request_options: request_options)
|
|
61
|
+
)
|
|
62
|
+
new(response, resolved_client)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class << self
|
|
66
|
+
alias retrieve find
|
|
67
|
+
alias info find
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Create a group from file UUIDs.
|
|
71
|
+
#
|
|
72
|
+
# @param uuids [Array<String>] File UUIDs
|
|
73
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
74
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
75
|
+
# @param options [Hash] Additional options
|
|
76
|
+
# @param request_options [Hash] Request options
|
|
77
|
+
# @return [Uploadcare::Resources::Group]
|
|
78
|
+
def self.create(uuids:, client: nil, config: Uploadcare.configuration, request_options: {}, **options)
|
|
79
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
80
|
+
response = Uploadcare::Result.unwrap(
|
|
81
|
+
resolved_client.api.upload.groups.create(
|
|
82
|
+
files: uuids, request_options: request_options, **options
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
new(response, resolved_client)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# --- Instance methods ---
|
|
89
|
+
|
|
90
|
+
# Reload group information from the API.
|
|
91
|
+
#
|
|
92
|
+
# @param request_options [Hash] Request options
|
|
93
|
+
# @return [self]
|
|
94
|
+
def reload(request_options: {})
|
|
95
|
+
response = Uploadcare::Result.unwrap(
|
|
96
|
+
client.api.rest.groups.info(uuid: id, request_options: request_options)
|
|
97
|
+
)
|
|
98
|
+
assign_attributes(response)
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
alias load reload
|
|
102
|
+
|
|
103
|
+
# Delete this group.
|
|
104
|
+
#
|
|
105
|
+
# @param request_options [Hash] Request options
|
|
106
|
+
# @return [nil]
|
|
107
|
+
def delete(request_options: {})
|
|
108
|
+
Uploadcare::Result.unwrap(
|
|
109
|
+
client.api.rest.groups.delete(uuid: id, request_options: request_options)
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns group ID, extracting from CDN URL if needed.
|
|
114
|
+
#
|
|
115
|
+
# @return [String, nil]
|
|
116
|
+
def id
|
|
117
|
+
return @id if @id
|
|
118
|
+
return @uuid if defined?(@uuid) && !@uuid.to_s.empty?
|
|
119
|
+
return unless @cdn_url
|
|
120
|
+
|
|
121
|
+
uri = URI.parse(@cdn_url)
|
|
122
|
+
@id = uri.path.split('/').reject(&:empty?).first
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns the CDN URL for this group.
|
|
126
|
+
#
|
|
127
|
+
# @return [String]
|
|
128
|
+
def cdn_url
|
|
129
|
+
return @cdn_url if @cdn_url && !@cdn_url.empty?
|
|
130
|
+
|
|
131
|
+
"#{config.cdn_base}#{id}/"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns CDN URLs for all files in the group.
|
|
135
|
+
#
|
|
136
|
+
# @return [Array<String>]
|
|
137
|
+
def file_cdn_urls
|
|
138
|
+
return [] if files_count.nil?
|
|
139
|
+
|
|
140
|
+
files_count.times.map { |i| "#{cdn_url}nth/#{i}/" }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Project resource representing the current Uploadcare project.
|
|
4
|
+
#
|
|
5
|
+
# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Project
|
|
6
|
+
class Uploadcare::Resources::Project < Uploadcare::Resources::BaseResource
|
|
7
|
+
attr_accessor :name, :pub_key, :autostore_enabled, :collaborators
|
|
8
|
+
|
|
9
|
+
# Get current project information.
|
|
10
|
+
#
|
|
11
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
12
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
13
|
+
# @param request_options [Hash] Request options
|
|
14
|
+
# @return [Uploadcare::Resources::Project]
|
|
15
|
+
def self.current(client: nil, config: Uploadcare.configuration, request_options: {})
|
|
16
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
17
|
+
response = Uploadcare::Result.unwrap(
|
|
18
|
+
resolved_client.api.rest.project.show(request_options: request_options)
|
|
19
|
+
)
|
|
20
|
+
new(response, resolved_client)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
alias show current
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Video conversion resource.
|
|
4
|
+
#
|
|
5
|
+
# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Video
|
|
6
|
+
class Uploadcare::Resources::VideoConversion < Uploadcare::Resources::BaseResource
|
|
7
|
+
attr_accessor :problems, :status, :error, :result
|
|
8
|
+
|
|
9
|
+
# Convert a video to a specified format (class method).
|
|
10
|
+
#
|
|
11
|
+
# @param params [Hash] Conversion parameters (:uuid, :format, :quality)
|
|
12
|
+
# @param options [Hash] Optional parameters (:store)
|
|
13
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
14
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
15
|
+
# @param request_options [Hash] Request options
|
|
16
|
+
# @return [Uploadcare::Resources::VideoConversion]
|
|
17
|
+
def self.convert(params:, options: {}, client: nil, config: Uploadcare.configuration, request_options: {})
|
|
18
|
+
raise ArgumentError, 'params must include :uuid' unless params[:uuid]
|
|
19
|
+
raise ArgumentError, 'params must include :format' unless params[:format]
|
|
20
|
+
raise ArgumentError, 'params must include :quality' unless params[:quality]
|
|
21
|
+
|
|
22
|
+
paths = Array(params[:uuid]).map do |uuid|
|
|
23
|
+
"#{uuid}/video/-/format/#{params[:format]}/-/quality/#{params[:quality]}/"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
27
|
+
response = Uploadcare::Result.unwrap(
|
|
28
|
+
resolved_client.api.rest.video_conversions.convert(
|
|
29
|
+
paths: paths, options: options, request_options: request_options
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
new(response, resolved_client)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Fetch video conversion status for a job token.
|
|
36
|
+
#
|
|
37
|
+
# @param token [String]
|
|
38
|
+
# @param client [Uploadcare::Client, nil]
|
|
39
|
+
# @param config [Uploadcare::Configuration]
|
|
40
|
+
# @param request_options [Hash]
|
|
41
|
+
# @return [Uploadcare::Resources::VideoConversion]
|
|
42
|
+
def self.status(token:, client: nil, config: Uploadcare.configuration, request_options: {})
|
|
43
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
44
|
+
new({}, resolved_client).fetch_status(token: token, request_options: request_options)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Refresh this resource from the conversion status endpoint.
|
|
48
|
+
#
|
|
49
|
+
# @param token [String]
|
|
50
|
+
# @param request_options [Hash]
|
|
51
|
+
# @return [self]
|
|
52
|
+
def fetch_status(token:, request_options: {})
|
|
53
|
+
response = Uploadcare::Result.unwrap(
|
|
54
|
+
client.api.rest.video_conversions.status(token: token, request_options: request_options)
|
|
55
|
+
)
|
|
56
|
+
assign_attributes(response)
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Webhook resource for managing Uploadcare webhooks.
|
|
4
|
+
#
|
|
5
|
+
# @see https://uploadcare.com/api-refs/rest-api/v0.7.0/#tag/Webhook
|
|
6
|
+
class Uploadcare::Resources::Webhook < Uploadcare::Resources::BaseResource
|
|
7
|
+
attr_accessor :id, :project, :created, :updated, :event, :target_url, :is_active, :signing_secret, :version
|
|
8
|
+
|
|
9
|
+
# List all project webhooks.
|
|
10
|
+
#
|
|
11
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
12
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
13
|
+
# @param request_options [Hash] Request options
|
|
14
|
+
# @return [Array<Uploadcare::Resources::Webhook>]
|
|
15
|
+
def self.list(client: nil, config: Uploadcare.configuration, request_options: {})
|
|
16
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
17
|
+
response = Uploadcare::Result.unwrap(
|
|
18
|
+
resolved_client.api.rest.webhooks.list(request_options: request_options)
|
|
19
|
+
)
|
|
20
|
+
response.map { |data| new(data, resolved_client) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Create a new webhook.
|
|
24
|
+
#
|
|
25
|
+
# @param target_url [String] Webhook target URL
|
|
26
|
+
# @param options [Hash] Webhook options
|
|
27
|
+
# @option options [String] :event Event type (default: "file.uploaded")
|
|
28
|
+
# @option options [Boolean] :is_active Active flag (default: true)
|
|
29
|
+
# @option options [String] :signing_secret Signing secret
|
|
30
|
+
# @option options [String] :version Webhook payload version
|
|
31
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
32
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
33
|
+
# @param request_options [Hash] Request options
|
|
34
|
+
# @return [Uploadcare::Resources::Webhook]
|
|
35
|
+
def self.create(target_url:, client: nil, config: Uploadcare.configuration, request_options: {}, **options)
|
|
36
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
37
|
+
event = options.fetch(:event, 'file.uploaded')
|
|
38
|
+
is_active = options.key?(:is_active) ? options[:is_active] : true
|
|
39
|
+
payload = { target_url: target_url, event: event, is_active: is_active }
|
|
40
|
+
payload[:signing_secret] = options[:signing_secret] if options[:signing_secret]
|
|
41
|
+
payload[:version] = options[:version] if options[:version]
|
|
42
|
+
|
|
43
|
+
response = Uploadcare::Result.unwrap(
|
|
44
|
+
resolved_client.api.rest.webhooks.create(options: payload, request_options: request_options)
|
|
45
|
+
)
|
|
46
|
+
new(response, resolved_client)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Update a webhook.
|
|
50
|
+
#
|
|
51
|
+
# @param id [Integer] Webhook ID
|
|
52
|
+
# @param options [Hash] Webhook options to update
|
|
53
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
54
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
55
|
+
# @param request_options [Hash] Request options
|
|
56
|
+
# @return [Uploadcare::Resources::Webhook]
|
|
57
|
+
def self.update(id:, client: nil, config: Uploadcare.configuration, request_options: {}, **options)
|
|
58
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
59
|
+
payload = options.slice(:target_url, :event, :signing_secret, :version)
|
|
60
|
+
payload[:is_active] = options[:is_active] if options.key?(:is_active)
|
|
61
|
+
|
|
62
|
+
response = Uploadcare::Result.unwrap(
|
|
63
|
+
resolved_client.api.rest.webhooks.update(id: id, options: payload, request_options: request_options)
|
|
64
|
+
)
|
|
65
|
+
new(response, resolved_client)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Delete a webhook by target URL.
|
|
69
|
+
#
|
|
70
|
+
# @param target_url [String] Target URL of the webhook to delete
|
|
71
|
+
# @param client [Uploadcare::Client, nil] Client instance
|
|
72
|
+
# @param config [Uploadcare::Configuration] Configuration fallback
|
|
73
|
+
# @param request_options [Hash] Request options
|
|
74
|
+
# @return [nil]
|
|
75
|
+
def self.delete(target_url:, client: nil, config: Uploadcare.configuration, request_options: {})
|
|
76
|
+
resolved_client = resolve_client(client: client, config: config)
|
|
77
|
+
Uploadcare::Result.unwrap(
|
|
78
|
+
resolved_client.api.rest.webhooks.delete(target_url: target_url, request_options: request_options)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class << self
|
|
83
|
+
alias unsubscribe delete
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Result wrapper for success/error handling.
|
|
4
|
+
class Uploadcare::Result
|
|
5
|
+
attr_reader :value, :error
|
|
6
|
+
|
|
7
|
+
def initialize(value: nil, error: nil)
|
|
8
|
+
@value = value
|
|
9
|
+
@error = error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Build a success result.
|
|
13
|
+
#
|
|
14
|
+
# @param value [Object]
|
|
15
|
+
# @return [Uploadcare::Result]
|
|
16
|
+
def self.success(value)
|
|
17
|
+
new(value: value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Build a failure result.
|
|
21
|
+
#
|
|
22
|
+
# @param error [Object]
|
|
23
|
+
# @return [Uploadcare::Result]
|
|
24
|
+
def self.failure(error)
|
|
25
|
+
new(error: error)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Capture exceptions and wrap in Result.
|
|
29
|
+
#
|
|
30
|
+
# @return [Uploadcare::Result]
|
|
31
|
+
def self.capture
|
|
32
|
+
success(yield)
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
failure(e)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Unwrap a Result or return the value as-is.
|
|
38
|
+
#
|
|
39
|
+
# @param value [Object]
|
|
40
|
+
# @return [Object]
|
|
41
|
+
def self.unwrap(value)
|
|
42
|
+
value.is_a?(Uploadcare::Result) ? value.value! : value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def success?
|
|
46
|
+
@error.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def failure?
|
|
50
|
+
!success?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Object] success value
|
|
54
|
+
def success
|
|
55
|
+
@value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Object] error value
|
|
59
|
+
def failure
|
|
60
|
+
@error
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Return the success value or raise the wrapped error.
|
|
64
|
+
#
|
|
65
|
+
# @return [Object]
|
|
66
|
+
# @raise [Exception, RuntimeError]
|
|
67
|
+
def value!
|
|
68
|
+
if failure?
|
|
69
|
+
error = @error
|
|
70
|
+
raise error if error.is_a?(Exception)
|
|
71
|
+
raise error if error.is_a?(String)
|
|
72
|
+
|
|
73
|
+
raise error.to_s
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
@value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [String, nil] error message
|
|
80
|
+
def error_message
|
|
81
|
+
return nil if @error.nil?
|
|
82
|
+
|
|
83
|
+
@error.respond_to?(:message) ? @error.message : @error.to_s
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -1,63 +1,67 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'openssl'
|
|
3
4
|
require_relative 'base_generator'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def build_expire
|
|
45
|
-
(Time.now.to_i + ttl).to_s
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def build_signature(expire, acl)
|
|
49
|
-
signature = ["exp=#{expire}", "acl=#{acl}"].join(delimiter)
|
|
50
|
-
secret_key_bin = Array(secret_key.gsub(/\s/, '')).pack('H*')
|
|
51
|
-
OpenSSL::HMAC.hexdigest(algorithm, secret_key_bin, signature)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# rubocop:disable Style/SlicingWithRange
|
|
55
|
-
def sanitized_string(string)
|
|
56
|
-
string = string[1..-1] if string[0] == '/'
|
|
57
|
-
string = string[0...-1] if string[-1] == '/'
|
|
58
|
-
string.strip
|
|
59
|
-
end
|
|
60
|
-
# rubocop:enable Style/SlicingWithRange
|
|
6
|
+
# Akamai signed URL generator.
|
|
7
|
+
class Uploadcare::SignedUrlGenerators::AkamaiGenerator < Uploadcare::SignedUrlGenerators::BaseGenerator
|
|
8
|
+
# UUID validation regex.
|
|
9
|
+
UUID_REGEX = /\A[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\z/i
|
|
10
|
+
# Akamai token template.
|
|
11
|
+
TEMPLATE = 'https://{cdn_host}/{uuid}/?token=exp={expiration}{delimiter}acl={acl}{delimiter}hmac={token}'
|
|
12
|
+
|
|
13
|
+
def generate_url(uuid, acl = uuid, wildcard: false)
|
|
14
|
+
raise ArgumentError, 'Must contain valid UUID' unless valid?(uuid)
|
|
15
|
+
|
|
16
|
+
formatted_acl = build_acl(uuid, acl, wildcard: wildcard)
|
|
17
|
+
expire = build_expire
|
|
18
|
+
signature = build_signature(expire, formatted_acl)
|
|
19
|
+
|
|
20
|
+
TEMPLATE.gsub('{delimiter}', delimiter)
|
|
21
|
+
.sub('{cdn_host}', sanitized_string(cdn_host))
|
|
22
|
+
.sub('{uuid}', sanitized_string(uuid))
|
|
23
|
+
.sub('{acl}', formatted_acl)
|
|
24
|
+
.sub('{expiration}', expire)
|
|
25
|
+
.sub('{token}', signature)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def valid?(uuid)
|
|
31
|
+
raise ArgumentError, 'Must contain valid UUID' unless uuid.is_a?(String)
|
|
32
|
+
|
|
33
|
+
UUID_REGEX.match?(uuid)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def delimiter
|
|
37
|
+
'~'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_acl(uuid, acl, wildcard: false)
|
|
41
|
+
if wildcard
|
|
42
|
+
"/#{sanitized_delimiter_path(uuid)}/*"
|
|
43
|
+
else
|
|
44
|
+
"/#{sanitized_delimiter_path(acl)}/"
|
|
61
45
|
end
|
|
62
46
|
end
|
|
47
|
+
|
|
48
|
+
def sanitized_delimiter_path(path)
|
|
49
|
+
sanitized_string(path).gsub('~') { |escape_char| "%#{escape_char.ord.to_s(16).downcase}" }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_expire
|
|
53
|
+
(Time.now.to_i + ttl).to_s
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_signature(expire, acl)
|
|
57
|
+
signature = ["exp=#{expire}", "acl=#{acl}"].join(delimiter)
|
|
58
|
+
secret_key_bin = Array(secret_key.delete(" \t\r\n")).pack('H*')
|
|
59
|
+
OpenSSL::HMAC.hexdigest(algorithm, secret_key_bin, signature)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def sanitized_string(string)
|
|
63
|
+
string = string[1..] if string[0] == '/'
|
|
64
|
+
string = string[0...-1] if string[-1] == '/'
|
|
65
|
+
string.strip
|
|
66
|
+
end
|
|
63
67
|
end
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
attr_reader :secret_key
|
|
3
|
+
# Base class for signed URL generators.
|
|
4
|
+
class Uploadcare::SignedUrlGenerators::BaseGenerator
|
|
5
|
+
attr_accessor :cdn_host, :ttl, :algorithm
|
|
6
|
+
attr_reader :secret_key
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
def initialize(cdn_host:, secret_key:, ttl: 300, algorithm: 'sha256')
|
|
9
|
+
@ttl = ttl
|
|
10
|
+
@algorithm = algorithm
|
|
11
|
+
@cdn_host = cdn_host
|
|
12
|
+
@secret_key = secret_key
|
|
13
|
+
end
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
# Generate a signed URL.
|
|
16
|
+
#
|
|
17
|
+
# @return [String]
|
|
18
|
+
def generate_url
|
|
19
|
+
raise NotImplementedError, "#{__method__} method not present"
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
# This object verifies a signature received along with webhook headers
|
|
6
|
+
class Uploadcare::WebhookSignatureVerifier
|
|
7
|
+
# @see https://uploadcare.com/docs/security/secure-webhooks/
|
|
8
|
+
def self.valid?(webhook_body: nil, signing_secret: nil, x_uc_signature_header: nil)
|
|
9
|
+
webhook_body_json = webhook_body
|
|
10
|
+
signing_secret ||= ENV.fetch('UC_SIGNING_SECRET', nil)
|
|
11
|
+
|
|
12
|
+
return false unless valid_parameters?(signing_secret, x_uc_signature_header, webhook_body_json)
|
|
13
|
+
|
|
14
|
+
calculated_signature = calculate_signature(signing_secret, webhook_body_json)
|
|
15
|
+
|
|
16
|
+
# Use constant-time comparison to prevent timing attacks
|
|
17
|
+
secure_compare?(calculated_signature, x_uc_signature_header)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Check if all required parameters are present and non-empty
|
|
21
|
+
# @param signing_secret [String] signing secret
|
|
22
|
+
# @param signature_header [String] signature from header
|
|
23
|
+
# @param body [String] webhook body
|
|
24
|
+
# @return [Boolean] true if all parameters are valid
|
|
25
|
+
def self.valid_parameters?(signing_secret, signature_header, body)
|
|
26
|
+
return false if signing_secret.nil? || signing_secret.to_s.empty?
|
|
27
|
+
return false if signature_header.nil? || signature_header.to_s.empty?
|
|
28
|
+
return false if body.nil? || body.to_s.empty?
|
|
29
|
+
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Calculate HMAC signature for webhook body
|
|
34
|
+
# @param secret [String] signing secret
|
|
35
|
+
# @param body [String] webhook body JSON
|
|
36
|
+
# @return [String] calculated signature
|
|
37
|
+
def self.calculate_signature(secret, body)
|
|
38
|
+
digest = OpenSSL::Digest.new('sha256')
|
|
39
|
+
"v1=#{OpenSSL::HMAC.hexdigest(digest, secret, body)}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Constant-time string comparison to prevent timing attacks
|
|
43
|
+
# @param first [String] first string
|
|
44
|
+
# @param second [String] second string
|
|
45
|
+
# @return [Boolean] true if strings are equal
|
|
46
|
+
def self.secure_compare?(first, second)
|
|
47
|
+
return false if first.nil? || second.nil?
|
|
48
|
+
return false unless first.bytesize == second.bytesize
|
|
49
|
+
|
|
50
|
+
OpenSSL.fixed_length_secure_compare(first, second)
|
|
51
|
+
rescue NoMethodError
|
|
52
|
+
result = 0
|
|
53
|
+
index = 0
|
|
54
|
+
while index < first.bytesize
|
|
55
|
+
result |= first.getbyte(index) ^ second.getbyte(index)
|
|
56
|
+
index += 1
|
|
57
|
+
end
|
|
58
|
+
result.zero?
|
|
59
|
+
end
|
|
60
|
+
end
|