databasus_ruby 0.0.1
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/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +305 -0
- data/Rakefile +12 -0
- data/lib/databasus_ruby/client.rb +123 -0
- data/lib/databasus_ruby/collection.rb +83 -0
- data/lib/databasus_ruby/endpoint.rb +99 -0
- data/lib/databasus_ruby/error.rb +63 -0
- data/lib/databasus_ruby/object.rb +99 -0
- data/lib/databasus_ruby/objects/auth.rb +66 -0
- data/lib/databasus_ruby/objects/backups.rb +67 -0
- data/lib/databasus_ruby/objects/databases.rb +107 -0
- data/lib/databasus_ruby/objects/notifiers.rb +59 -0
- data/lib/databasus_ruby/objects/storages.rb +55 -0
- data/lib/databasus_ruby/objects/users.rb +79 -0
- data/lib/databasus_ruby/objects/workspaces.rb +81 -0
- data/lib/databasus_ruby/version.rb +5 -0
- data/lib/databasus_ruby.rb +38 -0
- data/sig/databasus_ruby.rbs +285 -0
- metadata +81 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# Base class for every error raised by this gem.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when the client is missing something it needs before it can talk to
|
|
8
|
+
# the API at all (an agent id, a workspace id, ...).
|
|
9
|
+
class ConfigurationError < Error; end
|
|
10
|
+
|
|
11
|
+
# Raised when Databasus answers with a non-successful status code.
|
|
12
|
+
class APIError < Error
|
|
13
|
+
attr_reader :status, :body, :response
|
|
14
|
+
|
|
15
|
+
def initialize(message = nil, status: nil, body: nil, response: nil)
|
|
16
|
+
@status = status
|
|
17
|
+
@body = body
|
|
18
|
+
@response = response
|
|
19
|
+
super(message || "Databasus API returned #{status}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Maps a Faraday response onto the most specific error class available.
|
|
23
|
+
def self.from_response(response)
|
|
24
|
+
klass = for_status(response.status)
|
|
25
|
+
klass.new(message_from(response.body), status: response.status, body: response.body, response: response)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.for_status(status)
|
|
29
|
+
return ServerError if (500..599).cover?(status)
|
|
30
|
+
|
|
31
|
+
STATUS_ERRORS.fetch(status, APIError)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Every handler in the Go backend answers errors as {"error": "..."}, but
|
|
35
|
+
# streaming endpoints can still fail with a plain-text body.
|
|
36
|
+
def self.message_from(body)
|
|
37
|
+
case body
|
|
38
|
+
when Hash then body["error"] || body["message"] || body[:error]
|
|
39
|
+
when String then body.empty? ? nil : body[0, 500]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
private_class_method :message_from
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class BadRequestError < APIError; end
|
|
46
|
+
class AuthenticationError < APIError; end
|
|
47
|
+
class ForbiddenError < APIError; end
|
|
48
|
+
class NotFoundError < APIError; end
|
|
49
|
+
class ConflictError < APIError; end
|
|
50
|
+
class RateLimitError < APIError; end
|
|
51
|
+
class ServerError < APIError; end
|
|
52
|
+
|
|
53
|
+
class APIError
|
|
54
|
+
STATUS_ERRORS = {
|
|
55
|
+
400 => BadRequestError,
|
|
56
|
+
401 => AuthenticationError,
|
|
57
|
+
403 => ForbiddenError,
|
|
58
|
+
404 => NotFoundError,
|
|
59
|
+
409 => ConflictError,
|
|
60
|
+
429 => RateLimitError
|
|
61
|
+
}.freeze
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# A thin, duck-typed wrapper around a decoded JSON payload.
|
|
5
|
+
#
|
|
6
|
+
# database = client.databases.get(id)
|
|
7
|
+
# database.name # => "primary"
|
|
8
|
+
# database.workspace_id # => same as database.workspaceId
|
|
9
|
+
# database.postgresqlLogical.host
|
|
10
|
+
#
|
|
11
|
+
# Nested hashes (and arrays of hashes) are wrapped on access, so the whole
|
|
12
|
+
# tree reads the same way. The Databasus API omits empty fields, so reading a
|
|
13
|
+
# key that is not in the payload returns nil rather than raising.
|
|
14
|
+
class Object
|
|
15
|
+
def initialize(attributes = {})
|
|
16
|
+
@attributes = normalize(attributes)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_h
|
|
20
|
+
@attributes
|
|
21
|
+
end
|
|
22
|
+
alias to_hash to_h
|
|
23
|
+
|
|
24
|
+
def [](key)
|
|
25
|
+
fetch_attribute(key.to_s)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def key?(key)
|
|
29
|
+
return false if @attributes.nil?
|
|
30
|
+
|
|
31
|
+
@attributes.key?(key.to_s) || @attributes.key?(camelize(key.to_s))
|
|
32
|
+
end
|
|
33
|
+
alias has_key? key?
|
|
34
|
+
|
|
35
|
+
def keys
|
|
36
|
+
@attributes.nil? ? [] : @attributes.keys
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ==(other)
|
|
40
|
+
other.is_a?(Object) && to_h == other.to_h
|
|
41
|
+
end
|
|
42
|
+
alias eql? ==
|
|
43
|
+
|
|
44
|
+
def hash
|
|
45
|
+
to_h.hash
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def inspect
|
|
49
|
+
"#<#{self.class.name} #{keys.join(", ")}>"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def method_missing(name, *args, &block)
|
|
53
|
+
# Endpoint classes inherit from Object without carrying a payload; for
|
|
54
|
+
# them an unknown method is a genuine mistake, not a missing field.
|
|
55
|
+
return super if @attributes.nil?
|
|
56
|
+
return super unless args.empty? && block.nil?
|
|
57
|
+
|
|
58
|
+
fetch_attribute(name.to_s)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def respond_to_missing?(name, include_private = false)
|
|
62
|
+
@attributes.nil? ? super : true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def normalize(attributes)
|
|
68
|
+
return nil if attributes.nil?
|
|
69
|
+
return attributes.to_h.transform_keys(&:to_s) if attributes.respond_to?(:to_h)
|
|
70
|
+
|
|
71
|
+
raise ArgumentError, "expected a Hash-like payload, got #{attributes.class}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def fetch_attribute(key)
|
|
75
|
+
raw = if @attributes.key?(key)
|
|
76
|
+
@attributes[key]
|
|
77
|
+
else
|
|
78
|
+
@attributes[camelize(key)]
|
|
79
|
+
end
|
|
80
|
+
wrap(raw)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def wrap(value)
|
|
84
|
+
case value
|
|
85
|
+
when Hash then Object.new(value)
|
|
86
|
+
when ::Array then value.map { |item| wrap(item) }
|
|
87
|
+
else value
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# workspace_id -> workspaceId, so Ruby callers can stay in snake_case.
|
|
92
|
+
def camelize(key)
|
|
93
|
+
head, *rest = key.split("_")
|
|
94
|
+
return key if rest.empty?
|
|
95
|
+
|
|
96
|
+
head + rest.map(&:capitalize).join
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# Obtaining a token. Every other endpoint needs the JWT these methods
|
|
5
|
+
# return, so #signin and #signup install it on the client as `api_key`.
|
|
6
|
+
#
|
|
7
|
+
# client.auth.signin(email: "me@example.com", password: "secret")
|
|
8
|
+
# client.users.me.name
|
|
9
|
+
#
|
|
10
|
+
# Read the token off the result if you want to keep it for a later client.
|
|
11
|
+
class Auth < Object
|
|
12
|
+
include Endpoint
|
|
13
|
+
|
|
14
|
+
# POST /users/signin
|
|
15
|
+
def signin(email:, password:, cloudflare_turnstile_token: nil)
|
|
16
|
+
authorize object(http_post("users/signin", {
|
|
17
|
+
email: email,
|
|
18
|
+
password: password,
|
|
19
|
+
cloudflareTurnstileToken: cloudflare_turnstile_token
|
|
20
|
+
}))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# POST /users/signup
|
|
24
|
+
def signup(email:, name:, password:, cloudflare_turnstile_token: nil)
|
|
25
|
+
authorize object(http_post("users/signup", {
|
|
26
|
+
email: email,
|
|
27
|
+
name: name,
|
|
28
|
+
password: password,
|
|
29
|
+
cloudflareTurnstileToken: cloudflare_turnstile_token
|
|
30
|
+
}))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# POST /auth/github/callback
|
|
34
|
+
def github_callback(code:, redirect_uri:)
|
|
35
|
+
object(http_post("auth/github/callback", { code: code, redirectUri: redirect_uri }))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# POST /auth/google/callback
|
|
39
|
+
def google_callback(code:, redirect_uri:)
|
|
40
|
+
object(http_post("auth/google/callback", { code: code, redirectUri: redirect_uri }))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# POST /users/send-reset-password-code
|
|
44
|
+
def send_reset_password_code(email:, cloudflare_turnstile_token: nil)
|
|
45
|
+
object(http_post("users/send-reset-password-code", {
|
|
46
|
+
email: email,
|
|
47
|
+
cloudflareTurnstileToken: cloudflare_turnstile_token
|
|
48
|
+
}))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# POST /users/reset-password
|
|
52
|
+
def reset_password(email:, code:, new_password:)
|
|
53
|
+
object(http_post("users/reset-password", { email: email, code: code, newPassword: new_password }))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def authorize(result)
|
|
59
|
+
token = result.token
|
|
60
|
+
raise Error, "Databasus did not return a token" if token.nil? || token.empty?
|
|
61
|
+
|
|
62
|
+
client.api_key = token
|
|
63
|
+
result
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# Logical backups.
|
|
5
|
+
#
|
|
6
|
+
# backups = client.backups.list(database_id: id, status: %w[COMPLETED FAILED])
|
|
7
|
+
# backups.total
|
|
8
|
+
# backups.first.fileName
|
|
9
|
+
#
|
|
10
|
+
# Downloads are a two-step flow: mint a short-lived token, then stream the
|
|
11
|
+
# file with it. #download does both.
|
|
12
|
+
class Backups < Object
|
|
13
|
+
include Endpoint
|
|
14
|
+
|
|
15
|
+
STATUSES = %w[IN_PROGRESS COMPLETED FAILED CANCELED].freeze
|
|
16
|
+
ENCRYPTIONS = %w[NONE ENCRYPTED].freeze
|
|
17
|
+
VERIFICATION_STATUSES = %w[NOT_VERIFIED VERIFIED_SUCCESSFUL VERIFICATION_FAILED].freeze
|
|
18
|
+
|
|
19
|
+
# GET /backups — `status` accepts a single value or an array.
|
|
20
|
+
def list(database_id:, limit: nil, offset: nil, status: nil, before_date: nil, pg_wal_backup_type: nil)
|
|
21
|
+
response = http_get("backups", {
|
|
22
|
+
database_id: database_id,
|
|
23
|
+
limit: limit,
|
|
24
|
+
offset: offset,
|
|
25
|
+
status: status,
|
|
26
|
+
beforeDate: before_date,
|
|
27
|
+
pgWalBackupType: pg_wal_backup_type
|
|
28
|
+
})
|
|
29
|
+
collection(response, key: "backups")
|
|
30
|
+
end
|
|
31
|
+
alias all list
|
|
32
|
+
|
|
33
|
+
# POST /backups — queues a backup run for the database.
|
|
34
|
+
def create(database_id:)
|
|
35
|
+
object(http_post("backups", { database_id: database_id }))
|
|
36
|
+
end
|
|
37
|
+
alias trigger create
|
|
38
|
+
|
|
39
|
+
# DELETE /backups/{id}
|
|
40
|
+
def delete(id)
|
|
41
|
+
acknowledged(http_delete("backups/#{escape(id)}"))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# POST /backups/{id}/cancel
|
|
45
|
+
def cancel(id)
|
|
46
|
+
acknowledged(http_post("backups/#{escape(id)}/cancel"))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# POST /backups/{id}/download-token -> { token, filename, backupId }
|
|
50
|
+
def download_token(id)
|
|
51
|
+
object(http_post("backups/#{escape(id)}/download-token"))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# GET /backups/{id}/file — returns the raw body; pass a token from
|
|
55
|
+
# #download_token.
|
|
56
|
+
def file(id, token:)
|
|
57
|
+
http_get("backups/#{escape(id)}/file", { token: token }).body
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Mints a token and fetches the file in one call. Returns
|
|
61
|
+
# [filename, contents].
|
|
62
|
+
def download(id)
|
|
63
|
+
grant = download_token(id)
|
|
64
|
+
[grant.filename, file(id, token: grant.token)]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# Databases under backup.
|
|
5
|
+
#
|
|
6
|
+
# client.databases.list(workspace_id: id)
|
|
7
|
+
# client.databases.create(name: "primary", type: "POSTGRES_LOGICAL",
|
|
8
|
+
# workspaceId: id, postgresqlLogical: { ... })
|
|
9
|
+
#
|
|
10
|
+
# The create/update bodies are the full databases.Database payload, whose
|
|
11
|
+
# engine-specific half lives under one of `postgresqlLogical`,
|
|
12
|
+
# `postgresqlPhysical`, `mysql`, `mariadb` or `mongodb`.
|
|
13
|
+
class Databases < Object
|
|
14
|
+
include Endpoint
|
|
15
|
+
|
|
16
|
+
TYPES = %w[POSTGRES_LOGICAL POSTGRES_PHYSICAL MYSQL MARIADB MONGODB].freeze
|
|
17
|
+
HEALTH_STATUSES = %w[AVAILABLE UNAVAILABLE].freeze
|
|
18
|
+
|
|
19
|
+
# GET /databases
|
|
20
|
+
def list(workspace_id: nil)
|
|
21
|
+
collection(http_get("databases", { workspace_id: workspace_id || client.workspace_id! }), key: "databases")
|
|
22
|
+
end
|
|
23
|
+
alias all list
|
|
24
|
+
|
|
25
|
+
# GET /databases/{id}
|
|
26
|
+
def get(id)
|
|
27
|
+
object(http_get("databases/#{escape(id)}"))
|
|
28
|
+
end
|
|
29
|
+
alias find get
|
|
30
|
+
|
|
31
|
+
# POST /databases/create
|
|
32
|
+
def create(**attributes)
|
|
33
|
+
object(http_post("databases/create", with_default_workspace(attributes)))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# POST /databases/update — the id travels in the body, not the path.
|
|
37
|
+
def update(**attributes)
|
|
38
|
+
object(http_post("databases/update", attributes))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# DELETE /databases/{id}
|
|
42
|
+
def delete(id)
|
|
43
|
+
acknowledged(http_delete("databases/#{escape(id)}"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# POST /databases/{id}/copy
|
|
47
|
+
def copy(id)
|
|
48
|
+
object(http_post("databases/#{escape(id)}/copy"))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# POST /databases/{id}/test-connection — true when the database answers.
|
|
52
|
+
def test_connection(id)
|
|
53
|
+
acknowledged(http_post("databases/#{escape(id)}/test-connection"))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# POST /databases/test-connection-direct — tests a payload that has not
|
|
57
|
+
# been saved yet.
|
|
58
|
+
def test_connection_direct(**attributes)
|
|
59
|
+
acknowledged(http_post("databases/test-connection-direct", attributes))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# POST /databases/create-readonly-user -> { username, password }
|
|
63
|
+
def create_readonly_user(**attributes)
|
|
64
|
+
object(http_post("databases/create-readonly-user", attributes))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# POST /databases/create-replication-only-user (PostgreSQL physical only)
|
|
68
|
+
def create_replication_only_user(**attributes)
|
|
69
|
+
object(http_post("databases/create-replication-only-user", attributes))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# POST /databases/is-readonly -> { isReadOnly, privileges }
|
|
73
|
+
def readonly(**attributes)
|
|
74
|
+
object(http_post("databases/is-readonly", attributes))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def readonly?(**attributes)
|
|
78
|
+
readonly(**attributes).isReadOnly == true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The backups of one database, so a caller holding a database does not have
|
|
82
|
+
# to reach back for the other endpoint. Both delegate to Backups, which
|
|
83
|
+
# owns the requests.
|
|
84
|
+
#
|
|
85
|
+
# client.databases.backups(database.id, status: "COMPLETED")
|
|
86
|
+
# client.databases.backup(database.id) # queue a run
|
|
87
|
+
#
|
|
88
|
+
# #backups takes the same filters as Backups#list.
|
|
89
|
+
def backups(id, **filters)
|
|
90
|
+
client.backups.list(database_id: require_id(id), **filters)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def backup(id)
|
|
94
|
+
client.backups.create(database_id: require_id(id))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# GET /databases/notifier/{id}/databases-count
|
|
98
|
+
def notifier_databases_count(notifier_id)
|
|
99
|
+
single_value(http_get("databases/notifier/#{escape(notifier_id)}/databases-count"))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# GET /databases/notifier/{id}/is-using
|
|
103
|
+
def notifier_using?(notifier_id)
|
|
104
|
+
single_value(http_get("databases/notifier/#{escape(notifier_id)}/is-using")) == true
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# Notification channels.
|
|
5
|
+
#
|
|
6
|
+
# client.notifiers.save(name: "ops", notifierType: "SLACK",
|
|
7
|
+
# workspaceId: id, slackNotifier: { ... })
|
|
8
|
+
#
|
|
9
|
+
# POST /notifiers both creates and updates: include `id` in the payload to
|
|
10
|
+
# update an existing notifier.
|
|
11
|
+
class Notifiers < Object
|
|
12
|
+
include Endpoint
|
|
13
|
+
|
|
14
|
+
TYPES = %w[EMAIL TELEGRAM WEBHOOK SLACK DISCORD TEAMS].freeze
|
|
15
|
+
NOTIFICATION_TYPES = %w[
|
|
16
|
+
ALL BACKUP_SUCCESS BACKUP_FAILED HEALTHCHECK_SUCCESS HEALTHCHECK_FAILED
|
|
17
|
+
VERIFICATION_SUCCESS VERIFICATION_FAILED
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
# GET /notifiers
|
|
21
|
+
def list(workspace_id: nil)
|
|
22
|
+
collection(http_get("notifiers", { workspace_id: workspace_id || client.workspace_id! }), key: "notifiers")
|
|
23
|
+
end
|
|
24
|
+
alias all list
|
|
25
|
+
|
|
26
|
+
# GET /notifiers/{id}
|
|
27
|
+
def get(id)
|
|
28
|
+
object(http_get("notifiers/#{escape(id)}"))
|
|
29
|
+
end
|
|
30
|
+
alias find get
|
|
31
|
+
|
|
32
|
+
# POST /notifiers
|
|
33
|
+
def save(**attributes)
|
|
34
|
+
object(http_post("notifiers", with_default_workspace(attributes)))
|
|
35
|
+
end
|
|
36
|
+
alias create save
|
|
37
|
+
alias update save
|
|
38
|
+
|
|
39
|
+
# DELETE /notifiers/{id}
|
|
40
|
+
def delete(id)
|
|
41
|
+
acknowledged(http_delete("notifiers/#{escape(id)}"))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# POST /notifiers/{id}/test — sends a test notification.
|
|
45
|
+
def test(id)
|
|
46
|
+
acknowledged(http_post("notifiers/#{escape(id)}/test"))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# POST /notifiers/direct-test — tests a payload that has not been saved yet.
|
|
50
|
+
def direct_test(**attributes)
|
|
51
|
+
acknowledged(http_post("notifiers/direct-test", attributes))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# POST /notifiers/{id}/transfer
|
|
55
|
+
def transfer(id, target_workspace_id:)
|
|
56
|
+
acknowledged(http_post("notifiers/#{escape(id)}/transfer", { targetWorkspaceId: target_workspace_id }))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# Backup destinations.
|
|
5
|
+
#
|
|
6
|
+
# client.storages.save(name: "offsite", type: "S3",
|
|
7
|
+
# workspaceId: id, s3Storage: { ... })
|
|
8
|
+
#
|
|
9
|
+
# POST /storages both creates and updates: include `id` in the payload to
|
|
10
|
+
# update an existing storage.
|
|
11
|
+
class Storages < Object
|
|
12
|
+
include Endpoint
|
|
13
|
+
|
|
14
|
+
TYPES = %w[LOCAL S3 GOOGLE_DRIVE NAS AZURE_BLOB FTP SFTP RCLONE].freeze
|
|
15
|
+
|
|
16
|
+
# GET /storages
|
|
17
|
+
def list(workspace_id: nil)
|
|
18
|
+
collection(http_get("storages", { workspace_id: workspace_id || client.workspace_id! }), key: "storages")
|
|
19
|
+
end
|
|
20
|
+
alias all list
|
|
21
|
+
|
|
22
|
+
# GET /storages/{id}
|
|
23
|
+
def get(id)
|
|
24
|
+
object(http_get("storages/#{escape(id)}"))
|
|
25
|
+
end
|
|
26
|
+
alias find get
|
|
27
|
+
|
|
28
|
+
# POST /storages
|
|
29
|
+
def save(**attributes)
|
|
30
|
+
object(http_post("storages", with_default_workspace(attributes)))
|
|
31
|
+
end
|
|
32
|
+
alias create save
|
|
33
|
+
alias update save
|
|
34
|
+
|
|
35
|
+
# DELETE /storages/{id}
|
|
36
|
+
def delete(id)
|
|
37
|
+
acknowledged(http_delete("storages/#{escape(id)}"))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# POST /storages/{id}/test
|
|
41
|
+
def test(id)
|
|
42
|
+
acknowledged(http_post("storages/#{escape(id)}/test"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# POST /storages/direct-test — tests a payload that has not been saved yet.
|
|
46
|
+
def direct_test(**attributes)
|
|
47
|
+
acknowledged(http_post("storages/direct-test", attributes))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# POST /storages/{id}/transfer
|
|
51
|
+
def transfer(id, target_workspace_id:)
|
|
52
|
+
acknowledged(http_post("storages/#{escape(id)}/transfer", { targetWorkspaceId: target_workspace_id }))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# The signed-in user, instance-wide user administration and instance
|
|
5
|
+
# settings.
|
|
6
|
+
#
|
|
7
|
+
# client.users.me.email
|
|
8
|
+
# client.users.list(query: "ops", limit: 25).each { |user| ... }
|
|
9
|
+
#
|
|
10
|
+
# The #list / #get / #activate / #deactivate / #change_role and settings
|
|
11
|
+
# methods are ADMIN-only and answer 403 for a MEMBER token.
|
|
12
|
+
class Users < Object
|
|
13
|
+
include Endpoint
|
|
14
|
+
|
|
15
|
+
ROLES = %w[ADMIN MEMBER].freeze
|
|
16
|
+
|
|
17
|
+
# GET /users/me
|
|
18
|
+
def me
|
|
19
|
+
object(http_get("users/me"))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# PUT /users/me
|
|
23
|
+
def update_me(name: nil, email: nil)
|
|
24
|
+
object(http_put("users/me", { name: name, email: email }))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# PUT /users/change-password
|
|
28
|
+
def change_password(new_password:)
|
|
29
|
+
object(http_put("users/change-password", { newPassword: new_password }))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# POST /users/invite
|
|
33
|
+
def invite(email:, intended_workspace_id: nil, intended_workspace_role: nil)
|
|
34
|
+
object(http_post("users/invite", {
|
|
35
|
+
email: email,
|
|
36
|
+
intendedWorkspaceId: intended_workspace_id,
|
|
37
|
+
intendedWorkspaceRole: intended_workspace_role
|
|
38
|
+
}))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# GET /users (ADMIN)
|
|
42
|
+
def list(limit: nil, offset: nil, before_date: nil, query: nil)
|
|
43
|
+
response = http_get("users", { limit: limit, offset: offset, beforeDate: before_date, query: query })
|
|
44
|
+
collection(response, key: "users")
|
|
45
|
+
end
|
|
46
|
+
alias all list
|
|
47
|
+
|
|
48
|
+
# GET /users/{id} (ADMIN)
|
|
49
|
+
def get(id)
|
|
50
|
+
object(http_get("users/#{escape(id)}"))
|
|
51
|
+
end
|
|
52
|
+
alias find get
|
|
53
|
+
|
|
54
|
+
# POST /users/{id}/activate (ADMIN)
|
|
55
|
+
def activate(id)
|
|
56
|
+
acknowledged(http_post("users/#{escape(id)}/activate"))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# POST /users/{id}/deactivate (ADMIN)
|
|
60
|
+
def deactivate(id)
|
|
61
|
+
acknowledged(http_post("users/#{escape(id)}/deactivate"))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# PUT /users/{id}/role (ADMIN) — role is one of ROLES.
|
|
65
|
+
def change_role(id, role:)
|
|
66
|
+
acknowledged(http_put("users/#{escape(id)}/role", { role: role }))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# GET /users/settings (ADMIN) — instance-wide registration settings.
|
|
70
|
+
def settings
|
|
71
|
+
object(http_get("users/settings"))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# PUT /users/settings (ADMIN)
|
|
75
|
+
def update_settings(**attributes)
|
|
76
|
+
object(http_put("users/settings", attributes))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabasusRuby
|
|
4
|
+
# Workspaces, their members and their audit trail.
|
|
5
|
+
#
|
|
6
|
+
# workspace = client.workspaces.create(name: "production")
|
|
7
|
+
# client.workspaces.add_member(workspace.id, email: "ops@example.com",
|
|
8
|
+
# role: "WORKSPACE_ADMIN")
|
|
9
|
+
#
|
|
10
|
+
# Note that the membership endpoints live under /workspaces/memberships/{id},
|
|
11
|
+
# where {id} is still the workspace id.
|
|
12
|
+
class Workspaces < Object
|
|
13
|
+
include Endpoint
|
|
14
|
+
|
|
15
|
+
MEMBER_ROLES = %w[WORKSPACE_OWNER WORKSPACE_ADMIN WORKSPACE_MEMBER WORKSPACE_VIEWER].freeze
|
|
16
|
+
|
|
17
|
+
# GET /workspaces — the workspaces the current user belongs to.
|
|
18
|
+
def list
|
|
19
|
+
collection(http_get("workspaces"), key: "workspaces")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
alias all list
|
|
23
|
+
|
|
24
|
+
# POST /workspaces
|
|
25
|
+
def create(name:)
|
|
26
|
+
object(http_post("workspaces", { name: name }))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# GET /workspaces/{id}
|
|
30
|
+
def get(id)
|
|
31
|
+
object(http_get("workspaces/#{escape(id)}"))
|
|
32
|
+
end
|
|
33
|
+
alias find get
|
|
34
|
+
|
|
35
|
+
# PUT /workspaces/{id}
|
|
36
|
+
def update(id, **attributes)
|
|
37
|
+
object(http_put("workspaces/#{escape(id)}", attributes))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# DELETE /workspaces/{id}
|
|
41
|
+
def delete(id)
|
|
42
|
+
acknowledged(http_delete("workspaces/#{escape(id)}"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# GET /workspaces/{id}/audit-logs
|
|
46
|
+
def audit_logs(id, limit: nil, offset: nil, before_date: nil)
|
|
47
|
+
response = http_get("workspaces/#{escape(id)}/audit-logs",
|
|
48
|
+
{ limit: limit, offset: offset, beforeDate: before_date })
|
|
49
|
+
collection(response, key: "auditLogs")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# GET /workspaces/memberships/{id}/members
|
|
53
|
+
def members(id)
|
|
54
|
+
collection(http_get("workspaces/memberships/#{escape(id)}/members"), key: "members")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# POST /workspaces/memberships/{id}/members — adds an existing user or
|
|
58
|
+
# invites a new one; the response `status` says which happened
|
|
59
|
+
# (ADDED or INVITED). `role` is one of MEMBER_ROLES.
|
|
60
|
+
def add_member(id, email:, role:)
|
|
61
|
+
object(http_post("workspaces/memberships/#{escape(id)}/members", { email: email, role: role }))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# DELETE /workspaces/memberships/{id}/members/{userId}
|
|
65
|
+
def remove_member(id, user_id)
|
|
66
|
+
acknowledged(http_delete("workspaces/memberships/#{escape(id)}/members/#{escape(user_id)}"))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# PUT /workspaces/memberships/{id}/members/{userId}/role
|
|
70
|
+
def change_member_role(id, user_id, role:)
|
|
71
|
+
acknowledged(http_put("workspaces/memberships/#{escape(id)}/members/#{escape(user_id)}/role",
|
|
72
|
+
{ role: role }))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# POST /workspaces/memberships/{id}/transfer-ownership
|
|
76
|
+
def transfer_ownership(id, new_owner_email:)
|
|
77
|
+
acknowledged(http_post("workspaces/memberships/#{escape(id)}/transfer-ownership",
|
|
78
|
+
{ newOwnerEmail: new_owner_email }))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|