api2convert 10.2.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 +7 -0
- data/AGENTS.md +99 -0
- data/LICENSE +21 -0
- data/README.md +164 -0
- data/docs/CHANGELOG.md +9 -0
- data/docs/SDK_CONTRACT.md +99 -0
- data/examples/convert.rb +20 -0
- data/examples/webhook.rb +32 -0
- data/lib/api2convert/client.rb +139 -0
- data/lib/api2convert/config.rb +66 -0
- data/lib/api2convert/errors.rb +121 -0
- data/lib/api2convert/http/net_http_sender.rb +148 -0
- data/lib/api2convert/http/request.rb +47 -0
- data/lib/api2convert/http/response.rb +23 -0
- data/lib/api2convert/http/transport.rb +290 -0
- data/lib/api2convert/input_type.rb +16 -0
- data/lib/api2convert/job_status.rb +29 -0
- data/lib/api2convert/model/conversion.rb +30 -0
- data/lib/api2convert/model/input_file.rb +37 -0
- data/lib/api2convert/model/job.rb +66 -0
- data/lib/api2convert/model/job_message.rb +30 -0
- data/lib/api2convert/model/output_file.rb +40 -0
- data/lib/api2convert/model/preset.rb +32 -0
- data/lib/api2convert/model/status.rb +24 -0
- data/lib/api2convert/resource/contracts.rb +16 -0
- data/lib/api2convert/resource/conversions.rb +33 -0
- data/lib/api2convert/resource/jobs.rb +112 -0
- data/lib/api2convert/resource/presets.rb +43 -0
- data/lib/api2convert/resource/stats.rb +35 -0
- data/lib/api2convert/result.rb +185 -0
- data/lib/api2convert/support/data.rb +89 -0
- data/lib/api2convert/support/secret.rb +27 -0
- data/lib/api2convert/upload/file_uploader.rb +87 -0
- data/lib/api2convert/upload/multipart_stream.rb +74 -0
- data/lib/api2convert/version.rb +8 -0
- data/lib/api2convert/webhook/event.rb +23 -0
- data/lib/api2convert/webhook/verifier.rb +74 -0
- data/lib/api2convert.rb +62 -0
- data/openapi/api2convert.openapi.json +3325 -0
- metadata +88 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Model
|
|
5
|
+
# A single conversion within a job: the target format plus its options.
|
|
6
|
+
class Conversion
|
|
7
|
+
attr_reader :target, :id, :category, :options, :metadata
|
|
8
|
+
|
|
9
|
+
def initialize(target: "", id: nil, category: nil, options: {}, metadata: {})
|
|
10
|
+
@target = target
|
|
11
|
+
@id = id
|
|
12
|
+
@category = category
|
|
13
|
+
@options = options
|
|
14
|
+
@metadata = metadata
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.from_hash(data)
|
|
19
|
+
d = Support::Data.as_object(data)
|
|
20
|
+
new(
|
|
21
|
+
target: Support::Data.as_str(d["target"]),
|
|
22
|
+
id: Support::Data.nullable_str(d["id"]),
|
|
23
|
+
category: Support::Data.nullable_str(d["category"]),
|
|
24
|
+
options: Support::Data.as_object(d["options"]),
|
|
25
|
+
metadata: Support::Data.as_object(d["metadata"])
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Model
|
|
5
|
+
# An input file attached to a job.
|
|
6
|
+
class InputFile
|
|
7
|
+
attr_reader :id, :type, :source, :status, :filename, :size, :content_type, :options
|
|
8
|
+
|
|
9
|
+
def initialize(id: nil, type: "", source: nil, status: nil, filename: nil,
|
|
10
|
+
size: nil, content_type: nil, options: {})
|
|
11
|
+
@id = id
|
|
12
|
+
@type = type
|
|
13
|
+
@source = source
|
|
14
|
+
@status = status
|
|
15
|
+
@filename = filename
|
|
16
|
+
@size = size
|
|
17
|
+
@content_type = content_type
|
|
18
|
+
@options = options
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.from_hash(data)
|
|
23
|
+
d = Support::Data.as_object(data)
|
|
24
|
+
new(
|
|
25
|
+
id: Support::Data.nullable_str(d["id"]),
|
|
26
|
+
type: Support::Data.as_str(d["type"]),
|
|
27
|
+
source: Support::Data.nullable_str(d["source"]),
|
|
28
|
+
status: Support::Data.nullable_str(d["status"]),
|
|
29
|
+
filename: Support::Data.nullable_str(d["filename"]),
|
|
30
|
+
size: Support::Data.nullable_int(d["size"]),
|
|
31
|
+
content_type: Support::Data.nullable_str(d["content_type"]),
|
|
32
|
+
options: Support::Data.as_object(d["options"])
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Model
|
|
5
|
+
# A conversion job — the central API2Convert resource.
|
|
6
|
+
#
|
|
7
|
+
# {#server} and {#token} are needed to upload local files; {#output} holds the
|
|
8
|
+
# produced files once {#completed?}. {#raw} keeps the full decoded response for
|
|
9
|
+
# fields not surfaced as typed attributes.
|
|
10
|
+
class Job
|
|
11
|
+
attr_reader :id, :status, :token, :server, :callback,
|
|
12
|
+
:conversion, :input, :output, :errors, :warnings, :raw
|
|
13
|
+
|
|
14
|
+
def initialize(id:, status:, token:, server:, callback:,
|
|
15
|
+
conversion:, input:, output:, errors:, warnings:, raw:)
|
|
16
|
+
@id = id
|
|
17
|
+
@status = status
|
|
18
|
+
@token = token
|
|
19
|
+
@server = server
|
|
20
|
+
@callback = callback
|
|
21
|
+
@conversion = conversion
|
|
22
|
+
@input = input
|
|
23
|
+
@output = output
|
|
24
|
+
@errors = errors
|
|
25
|
+
@warnings = warnings
|
|
26
|
+
@raw = raw
|
|
27
|
+
freeze
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.from_hash(data)
|
|
31
|
+
d = Support::Data.as_object(data)
|
|
32
|
+
new(
|
|
33
|
+
id: Support::Data.as_str(d["id"]),
|
|
34
|
+
status: Status.from_hash(d["status"]),
|
|
35
|
+
token: Support::Data.nullable_str(d["token"]),
|
|
36
|
+
server: Support::Data.nullable_str(d["server"]),
|
|
37
|
+
callback: Support::Data.nullable_str(d["callback"]),
|
|
38
|
+
conversion: Support::Data.map_objects(d["conversion"]) { |x| Conversion.from_hash(x) },
|
|
39
|
+
input: Support::Data.map_objects(d["input"]) { |x| InputFile.from_hash(x) },
|
|
40
|
+
output: Support::Data.map_objects(d["output"]) { |x| OutputFile.from_hash(x) },
|
|
41
|
+
errors: Support::Data.map_objects(d["errors"]) { |x| JobMessage.from_hash(x) },
|
|
42
|
+
warnings: Support::Data.map_objects(d["warnings"]) { |x| JobMessage.from_hash(x) },
|
|
43
|
+
raw: d
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def completed?
|
|
48
|
+
@status.code == JobStatus::COMPLETED
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def failed?
|
|
52
|
+
@status.code == JobStatus::FAILED
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The job was canceled server-side — terminal, and produced no output.
|
|
56
|
+
def canceled?
|
|
57
|
+
@status.code == JobStatus::CANCELED
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Finished (completed, failed or canceled) and will not change further.
|
|
61
|
+
def terminal?
|
|
62
|
+
JobStatus.terminal?(@status.code)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Model
|
|
5
|
+
# An error or warning attached to a job (the `errors[]` / `warnings[]` entries).
|
|
6
|
+
class JobMessage
|
|
7
|
+
attr_reader :code, :message, :source, :id_source, :details
|
|
8
|
+
|
|
9
|
+
def initialize(code: nil, message: "", source: nil, id_source: nil, details: {})
|
|
10
|
+
@code = code
|
|
11
|
+
@message = message
|
|
12
|
+
@source = source
|
|
13
|
+
@id_source = id_source
|
|
14
|
+
@details = details
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.from_hash(data)
|
|
19
|
+
d = Support::Data.as_object(data)
|
|
20
|
+
new(
|
|
21
|
+
code: Support::Data.nullable_int(d["code"]),
|
|
22
|
+
message: Support::Data.as_str(d["message"]),
|
|
23
|
+
source: Support::Data.nullable_str(d["source"]),
|
|
24
|
+
id_source: Support::Data.nullable_str(d["id_source"]),
|
|
25
|
+
details: Support::Data.as_object(d["details"])
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Model
|
|
5
|
+
# A produced output file.
|
|
6
|
+
#
|
|
7
|
+
# {#uri} is a self-contained download URL (no auth), valid for a limited time
|
|
8
|
+
# (24h by default).
|
|
9
|
+
class OutputFile
|
|
10
|
+
attr_reader :id, :uri, :filename, :size, :status, :content_type, :checksum, :metadata
|
|
11
|
+
|
|
12
|
+
def initialize(id: nil, uri: "", filename: nil, size: nil, status: nil,
|
|
13
|
+
content_type: nil, checksum: nil, metadata: {})
|
|
14
|
+
@id = id
|
|
15
|
+
@uri = uri
|
|
16
|
+
@filename = filename
|
|
17
|
+
@size = size
|
|
18
|
+
@status = status
|
|
19
|
+
@content_type = content_type
|
|
20
|
+
@checksum = checksum
|
|
21
|
+
@metadata = metadata
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.from_hash(data)
|
|
26
|
+
d = Support::Data.as_object(data)
|
|
27
|
+
new(
|
|
28
|
+
id: Support::Data.nullable_str(d["id"]),
|
|
29
|
+
uri: Support::Data.as_str(d["uri"]),
|
|
30
|
+
filename: Support::Data.nullable_str(d["filename"]),
|
|
31
|
+
size: Support::Data.nullable_int(d["size"]),
|
|
32
|
+
status: Support::Data.nullable_str(d["status"]),
|
|
33
|
+
content_type: Support::Data.nullable_str(d["content_type"]),
|
|
34
|
+
checksum: Support::Data.nullable_str(d["checksum"]),
|
|
35
|
+
metadata: Support::Data.as_object(d["metadata"])
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Model
|
|
5
|
+
# A saved conversion preset (a reusable named set of target + options).
|
|
6
|
+
class Preset
|
|
7
|
+
attr_reader :id, :name, :target, :category, :scope, :options
|
|
8
|
+
|
|
9
|
+
def initialize(id: nil, name: "", target: nil, category: nil, scope: nil, options: {})
|
|
10
|
+
@id = id
|
|
11
|
+
@name = name
|
|
12
|
+
@target = target
|
|
13
|
+
@category = category
|
|
14
|
+
@scope = scope
|
|
15
|
+
@options = options
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.from_hash(data)
|
|
20
|
+
d = Support::Data.as_object(data)
|
|
21
|
+
new(
|
|
22
|
+
id: Support::Data.nullable_str(d["id"]),
|
|
23
|
+
name: Support::Data.as_str(d["name"]),
|
|
24
|
+
target: Support::Data.nullable_str(d["target"]),
|
|
25
|
+
category: Support::Data.nullable_str(d["category"]),
|
|
26
|
+
scope: Support::Data.nullable_str(d["scope"]),
|
|
27
|
+
options: Support::Data.as_object(d["options"])
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Model
|
|
5
|
+
# A job's status: a machine-readable {#code} plus optional human {#info}.
|
|
6
|
+
class Status
|
|
7
|
+
attr_reader :code, :info
|
|
8
|
+
|
|
9
|
+
def initialize(code: "", info: nil)
|
|
10
|
+
@code = code
|
|
11
|
+
@info = info
|
|
12
|
+
freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.from_hash(data)
|
|
16
|
+
d = Support::Data.as_object(data)
|
|
17
|
+
new(
|
|
18
|
+
code: Support::Data.as_str(d["code"]),
|
|
19
|
+
info: Support::Data.nullable_str(d["info"])
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Resource
|
|
5
|
+
# Information about the account's active contracts (free-form response).
|
|
6
|
+
class Contracts
|
|
7
|
+
def initialize(transport)
|
|
8
|
+
@transport = transport
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get
|
|
12
|
+
@transport.request("GET", "/contracts")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Resource
|
|
5
|
+
# The conversions catalog (`GET /conversions`).
|
|
6
|
+
#
|
|
7
|
+
# The source of truth for which targets exist and which options each accepts.
|
|
8
|
+
class Conversions
|
|
9
|
+
def initialize(transport)
|
|
10
|
+
@transport = transport
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# List supported conversions, optionally filtered by category/target. Each
|
|
14
|
+
# entry: `{ id, category, target, options }`.
|
|
15
|
+
def list(category = nil, target = nil, page = 1)
|
|
16
|
+
query = { "page" => page.to_s }
|
|
17
|
+
query["category"] = category unless category.nil?
|
|
18
|
+
query["target"] = target unless target.nil?
|
|
19
|
+
rows = @transport.request("GET", "/conversions", nil, query)
|
|
20
|
+
Support::Data.as_list(rows).grep(Hash)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The option schema (type / enum / default / range) for a single target.
|
|
24
|
+
# +category+ is optional — pass it only to disambiguate an ambiguous target.
|
|
25
|
+
def options(target, category = nil)
|
|
26
|
+
rows = list(category, target)
|
|
27
|
+
first = rows.first || {}
|
|
28
|
+
opts = first["options"]
|
|
29
|
+
opts.is_a?(Hash) ? opts : {}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Resource
|
|
5
|
+
# Full control over the job lifecycle.
|
|
6
|
+
#
|
|
7
|
+
# Most users only need `client.convert`, which is built on top of these
|
|
8
|
+
# methods. Reach for this resource for compound jobs, merges, presets, custom
|
|
9
|
+
# polling or job chaining. Methods are thin: build the request, call the
|
|
10
|
+
# transport, hydrate a model.
|
|
11
|
+
class Jobs
|
|
12
|
+
def initialize(transport, uploader)
|
|
13
|
+
@transport = transport
|
|
14
|
+
@uploader = uploader
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Create a job. Pass `{ "process" => false }` to stage it for uploads, then
|
|
18
|
+
# call {#start} once inputs are attached. +idempotency_key+ makes the create
|
|
19
|
+
# retry-safe (sent as the `Idempotency-Key` header).
|
|
20
|
+
def create(payload, idempotency_key = nil)
|
|
21
|
+
headers = idempotency_key.nil? ? nil : { "Idempotency-Key" => idempotency_key }
|
|
22
|
+
Model::Job.from_hash(@transport.request("POST", "/jobs", payload, nil, headers))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get(job_id)
|
|
26
|
+
Model::Job.from_hash(@transport.request("GET", "/jobs/#{Support::Data.encode_segment(job_id)}"))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# List the current key's jobs (paginated, 50 per page).
|
|
30
|
+
def list(status = nil, page = 1)
|
|
31
|
+
query = { "page" => page.to_s }
|
|
32
|
+
query["status"] = status unless status.nil?
|
|
33
|
+
rows = @transport.request("GET", "/jobs", nil, query)
|
|
34
|
+
hashes(rows).map { |row| Model::Job.from_hash(row) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update(job_id, payload)
|
|
38
|
+
Model::Job.from_hash(
|
|
39
|
+
@transport.request("PATCH", "/jobs/#{Support::Data.encode_segment(job_id)}", payload)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Start processing a staged job (`process => true`).
|
|
44
|
+
def start(job_id)
|
|
45
|
+
update(job_id, { "process" => true })
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Cancel a job (whether staged or processing).
|
|
49
|
+
def cancel(job_id)
|
|
50
|
+
@transport.request("DELETE", "/jobs/#{Support::Data.encode_segment(job_id)}")
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Attach an input by descriptor, e.g. a remote URL:
|
|
55
|
+
# `add_input(job_id, { "type" => "remote", "source" => "https://..." })`.
|
|
56
|
+
def add_input(job_id, descriptor)
|
|
57
|
+
Model::InputFile.from_hash(
|
|
58
|
+
@transport.request("POST", "/jobs/#{Support::Data.encode_segment(job_id)}/input", descriptor)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Upload a local file (path or IO) to the job's upload server.
|
|
63
|
+
def upload(job, file, filename = nil)
|
|
64
|
+
@uploader.upload(job, file, filename)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Block until the job reaches a terminal status, polling with backoff.
|
|
68
|
+
#
|
|
69
|
+
# Raises {ConversionFailedError} on a failed/canceled job (unless
|
|
70
|
+
# +throw_on_failure+ is false) and {ConversionTimeoutError} past the
|
|
71
|
+
# deadline. The interval is floored and the total wait capped, so no
|
|
72
|
+
# configuration can busy-loop or poll unbounded.
|
|
73
|
+
def wait(job_id, timeout_seconds = nil, throw_on_failure = true)
|
|
74
|
+
config = @transport.config
|
|
75
|
+
# Clamp again here (Config.create already clamps) so a directly-built
|
|
76
|
+
# Config or a per-call override can never busy-loop or poll unbounded.
|
|
77
|
+
requested = timeout_seconds.nil? ? config.poll_timeout : timeout_seconds
|
|
78
|
+
timeout = requested.clamp(0, Config::MAX_POLL_TIMEOUT)
|
|
79
|
+
max_interval = [Config::MIN_POLL_INTERVAL, config.poll_max_interval].max
|
|
80
|
+
interval = [Config::MIN_POLL_INTERVAL, config.poll_interval].max
|
|
81
|
+
deadline = monotonic + timeout
|
|
82
|
+
|
|
83
|
+
loop do
|
|
84
|
+
job = get(job_id)
|
|
85
|
+
|
|
86
|
+
raise ConversionFailedError.new(job) if (job.failed? || job.canceled?) && throw_on_failure
|
|
87
|
+
return job if job.terminal?
|
|
88
|
+
raise ConversionTimeoutError.new(job, timeout) if monotonic >= deadline
|
|
89
|
+
|
|
90
|
+
@transport.pause(interval)
|
|
91
|
+
interval = [max_interval, interval * 1.5].min
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Outputs produced by the job (use {#get} or {#wait} first).
|
|
96
|
+
def outputs(job_id)
|
|
97
|
+
rows = @transport.request("GET", "/jobs/#{Support::Data.encode_segment(job_id)}/output")
|
|
98
|
+
hashes(rows).map { |row| Model::OutputFile.from_hash(row) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def hashes(rows)
|
|
104
|
+
Support::Data.as_list(rows).grep(Hash)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def monotonic
|
|
108
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Resource
|
|
5
|
+
# Saved conversion presets (reusable named target + options).
|
|
6
|
+
class Presets
|
|
7
|
+
def initialize(transport)
|
|
8
|
+
@transport = transport
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def list(category: nil, target: nil, filter: nil)
|
|
12
|
+
query = {}
|
|
13
|
+
query["category"] = category unless category.nil?
|
|
14
|
+
query["target"] = target unless target.nil?
|
|
15
|
+
query["filter"] = filter unless filter.nil?
|
|
16
|
+
rows = @transport.request("GET", "/presets", nil, query)
|
|
17
|
+
Support::Data.as_list(rows).grep(Hash).map { |row| Model::Preset.from_hash(row) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Create a preset from `{ name, target, options, scope?, category? }`.
|
|
21
|
+
def create(payload)
|
|
22
|
+
Model::Preset.from_hash(@transport.request("POST", "/presets", payload))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get(preset_id)
|
|
26
|
+
Model::Preset.from_hash(
|
|
27
|
+
@transport.request("GET", "/presets/#{Support::Data.encode_segment(preset_id)}")
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update(preset_id, payload)
|
|
32
|
+
Model::Preset.from_hash(
|
|
33
|
+
@transport.request("PATCH", "/presets/#{Support::Data.encode_segment(preset_id)}", payload)
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def delete(preset_id)
|
|
38
|
+
@transport.request("DELETE", "/presets/#{Support::Data.encode_segment(preset_id)}")
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Api2Convert
|
|
4
|
+
module Resource
|
|
5
|
+
# API usage statistics. The response shape is free-form (returned as-is).
|
|
6
|
+
#
|
|
7
|
+
# +filter+ is either an API key to scope to, or `"all"`.
|
|
8
|
+
class Stats
|
|
9
|
+
def initialize(transport)
|
|
10
|
+
@transport = transport
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# +day+ format `yyyy-mm-dd`.
|
|
14
|
+
def day(day, filter = "all")
|
|
15
|
+
@transport.request("GET", "/stats/day/#{seg(day)}/#{seg(filter)}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# +month+ format `yyyy-mm`.
|
|
19
|
+
def month(month, filter = "all")
|
|
20
|
+
@transport.request("GET", "/stats/month/#{seg(month)}/#{seg(filter)}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# +year+ format `yyyy`.
|
|
24
|
+
def year(year, filter = "all")
|
|
25
|
+
@transport.request("GET", "/stats/year/#{seg(year)}/#{seg(filter)}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def seg(value)
|
|
31
|
+
Support::Data.encode_segment(value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|