databasus_ruby 0.0.1 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9223ce08d06f8a4f29e700d9046bab99ee5aaec01131af37a12620cdec27b7ed
4
- data.tar.gz: 1cd2c4226183d6106dd8a351e61533c2bdf513f818bb0e43f4c3dd077fe548b0
3
+ metadata.gz: c06a2f99b31ebac53becd79a06a01790a65e106890f2275f0645548197c33611
4
+ data.tar.gz: 14433d80684697a781025fb4dce7f0839fe5448dbc6cbef9f7c52e257b52c8b9
5
5
  SHA512:
6
- metadata.gz: f4c7dda68f74e63dc5833e41b2aff40c730f5462920bc8aaee57549a0b435516d187d07bd6e62591d7ef10b03f863d2492d5754da422021089bcaa4226d26fe2
7
- data.tar.gz: 5c59d0c7e223f8904a54de677f5083c9cd94466e16e60d082b02ef42124784e52f28c09922fbe33542bf5e7b5f5c07c3b2e7a21a5d675e96e46cda29715ba7e7
6
+ metadata.gz: 25a821696e164674b457941926ae8940ac8656af9106409fe9d63c7c2bb2f429805e52ab057725876f8e57697566d6327a4fe0b55604342cd9ac3793939953c1
7
+ data.tar.gz: a2b67bc8e87d04c3306b4c1198f4352f7cdd2f5af7177c456df0e5c23c46d808a38f17aa35ab1875b2cd5db281c4c4718193f14225ed4265347b6189cc4fcf06
data/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-11-13
4
+
5
+ Targets the REST API of Databasus 3.65 and later. Databasus 3.65 replaced its
6
+ API (new paths, cursor pagination, RFC 9457 problem responses), so this release
7
+ does not talk to 3.64 or earlier: pin `~> 0.1.0` for those.
8
+
9
+ ### Added
10
+
11
+ - `Restores` endpoint (`start`, `get`, `list`, `cancel`) and
12
+ `Restores.postgresql_target`, which restores without ownership and
13
+ privileges by default.
14
+ - `Backups#download_to(id, io)` and `Client#stream`: backups stream to an IO
15
+ instead of being read into memory.
16
+ - `Backups#get`; `Databases#list_all`, which follows the cursor; `Databases#test`
17
+ / `#connected?`; `Storages#backup_usage`; `Auth#verify_sign_in_code`.
18
+ - `APIError#code` (the problem's `code`); validation field errors are appended
19
+ to the message.
20
+ - `SignInCodeRequiredError`, raised by `Auth#signin` when the account needs an
21
+ emailed sign-in code.
22
+ - `Collection#next_cursor`, `#last_page?` and `#extras` (page-level fields such
23
+ as the backups page's `totalUsageMb`).
24
+
25
+ ### Changed
26
+
27
+ - Every path follows the 3.65 API: `POST /sessions`, `/logical-backups`,
28
+ `POST /download-tokens`, `PUT /databases/{id}`, `/workspace-members`,
29
+ `/audit-events`, `/instance-settings`, `/password-resets`.
30
+ - List methods take `limit:` / `after:` instead of `limit:` / `offset:`.
31
+ - Array query parameters repeat the key (`status=A&status=B`).
32
+ - `Notifiers` and `Storages` split `save` into `create` and `update(id, ...)`.
33
+ - Renamed: `Auth#send_reset_password_code` → `#request_password_reset`,
34
+ `Databases#test_connection` → `#test`, `Workspaces#audit_logs` → `#audit_events` (filter with `author_user_id:`).
35
+ - Methods now take the target id positionally: `Databases#update(id, ...)`,
36
+ `Users#update(id, name:, email:)`, `Users#change_password(id, new_password:)`,
37
+ `Workspaces#update(id, name:)`.
38
+ - Workspace membership is addressed by member id: `remove_member(member_id)`,
39
+ `change_member_role(member_id, role:)`, and
40
+ `transfer_ownership(id, new_owner_member_id:)` replaces `new_owner_email:`.
41
+ - `Backups#list` filters with `created_before:` (was `before_date:`);
42
+ `Databases#list` accepts `notifier_id:`.
43
+ - `APIError` messages come from the problem's `detail` / `title`;
44
+ `APIError.from_response` accepts a `body:` override for streamed responses.
45
+ - JSON responses are parsed for any `*json*` content type
46
+ (e.g. `application/problem+json`).
47
+
48
+ ### Removed
49
+
50
+ - Offset pagination (`Collection#total`, `#limit`, `#offset`, `#next_offset`).
51
+ - Endpoints the API dropped: OAuth callbacks (`Auth#github_callback`,
52
+ `#google_callback`), `Users#me`, `#update_me`, `#invite`, readonly and
53
+ replication users (`Databases#create_readonly_user`,
54
+ `#create_replication_only_user`, `#readonly`, `#readonly?`), `Databases#test_connection_direct`,
55
+ notifier usage counts (`Databases#notifier_databases_count`,
56
+ `#notifier_using?`), and the `direct_test` methods of `Notifiers` and
57
+ `Storages`.
58
+
59
+ ## [0.1.0]
60
+
3
61
  ### Added
4
62
 
5
63
  - `DatabasusRuby::Client` with bearer authentication, an optional default
data/README.md CHANGED
@@ -109,10 +109,18 @@ A database can reach its own backups, which delegates to the `backups` endpoint
109
109
  with the same filters as `client.backups.list`:
110
110
 
111
111
  ```ruby
112
+ database.backups(status: "COMPLETED", limit: 25)
113
+ database.backup # queue a run
114
+
115
+ # or by id, from the endpoint
112
116
  client.databases.backups(database.id, status: "COMPLETED", limit: 25)
113
- client.databases.backup(database.id) # queue a run
117
+ client.databases.backup(database.id)
114
118
  ```
115
119
 
120
+ `list`, `get`, `create`, `update` and `copy` return `DatabasusRuby::Database`
121
+ records: payload objects that read exactly like any other response but also
122
+ carry the two methods above.
123
+
116
124
  `update` posts to `/databases/update` with the id in the body, matching the API.
117
125
  There is also `test_connection_direct`, `create_readonly_user`,
118
126
  `create_replication_only_user` and `readonly?` for a payload you have not saved
@@ -4,16 +4,14 @@ module DatabasusRuby
4
4
  # Entry point for the gem. Holds the connection and hands out one endpoint
5
5
  # object per API domain.
6
6
  #
7
- # client = DatabasusRuby::Client.new(
8
- # api_key: ENV["DATABASUS_API_KEY"],
9
- # url: "https://databasus.example.com/api/v1/"
10
- # )
7
+ # client = DatabasusRuby::Client.new(url: "https://databasus.example.com/api/v1/")
8
+ # client.auth.signin(email: "ops@example.com", password: "secret")
11
9
  # client.databases.list(workspace_id: id)
12
10
  #
13
- # `api_key` is sent as `Authorization: Bearer <api_key>`. Databasus accepts
14
- # two kinds of bearer token there: a user JWT (from Auth#signin, used by
15
- # every regular endpoint) and a verification-agent token (used by the
16
- # /agent/* endpoints, which also need `agent_id`).
11
+ # Targets the Databasus REST API introduced in 3.65 (OpenAPI, cursor
12
+ # pagination, RFC 9457 problem responses). `api_key` is sent as
13
+ # `Authorization: Bearer <api_key>`; Auth#signin installs the session token
14
+ # there.
17
15
  class Client
18
16
  attr_reader :adapter, :url
19
17
  attr_accessor :api_key, :workspace_id
@@ -30,8 +28,11 @@ module DatabasusRuby
30
28
  def connection
31
29
  @connection ||= Faraday.new do |conn|
32
30
  conn.url_prefix = url
31
+ # Array filters (`status=A&status=B`) are form-style with explode,
32
+ # which the nested encoder would send as `status[]=`.
33
+ conn.options.params_encoder = Faraday::FlatParamsEncoder
33
34
  conn.request :json
34
- conn.response :json, content_type: "application/json"
35
+ conn.response :json, content_type: /\bjson\b/
35
36
  conn.adapter adapter
36
37
  end
37
38
  end
@@ -52,6 +53,10 @@ module DatabasusRuby
52
53
  @notifiers ||= Notifiers.new(self)
53
54
  end
54
55
 
56
+ def restores
57
+ @restores ||= Restores.new(self)
58
+ end
59
+
55
60
  def storages
56
61
  @storages ||= Storages.new(self)
57
62
  end
@@ -64,8 +69,8 @@ module DatabasusRuby
64
69
  @workspaces ||= Workspaces.new(self)
65
70
  end
66
71
 
67
- # The workspace-scoped list endpoints all require a workspace id; callers
68
- # can either pass one per call or set a default on the client.
72
+ # The workspace-scoped list endpoints filter by workspace; callers can
73
+ # either pass one per call or set a default on the client.
69
74
  def workspace_id!
70
75
  return workspace_id unless workspace_id.nil? || workspace_id.to_s.empty?
71
76
 
@@ -78,9 +83,8 @@ module DatabasusRuby
78
83
  # decide how to interpret the body.
79
84
  def request(method, path, params: nil, body: nil, headers: nil)
80
85
  response = connection.public_send(method, path) do |req|
81
- req.params.update(stringify(params)) if params
86
+ prepare(req, params: params, headers: headers)
82
87
  req.body = body unless body.nil?
83
- req.headers.update(request_headers(headers))
84
88
  end
85
89
 
86
90
  raise APIError.from_response(response) unless response.success?
@@ -88,15 +92,67 @@ module DatabasusRuby
88
92
  response
89
93
  end
90
94
 
95
+ # GETs `path` and writes the body to `io` chunk by chunk, so a multi-GB
96
+ # backup never sits in memory. An error answer is collected instead and
97
+ # raised as the matching APIError. Returns the number of bytes written.
98
+ def stream(path, io:, params: nil)
99
+ sink = StreamSink.new(io)
100
+ response = connection.get(path) do |req|
101
+ prepare(req, params: params)
102
+ # Faraday streams only when on_data is a Proc (RequestOptions#stream_response?).
103
+ req.options.on_data = sink.to_proc
104
+ end
105
+ return sink.written if response.success?
106
+
107
+ raise APIError.from_response(response, body: sink.decoded_error_body)
108
+ end
109
+
110
+ # Routes streamed chunks: a 2xx body goes to the caller's io, anything else
111
+ # is kept so it can be raised as an APIError.
112
+ class StreamSink
113
+ attr_reader :written, :error_body
114
+
115
+ def initialize(io)
116
+ @io = io
117
+ @written = 0
118
+ @error_body = +""
119
+ end
120
+
121
+ def to_proc
122
+ proc { |chunk, _received, env| write(chunk, env) }
123
+ end
124
+
125
+ def decoded_error_body
126
+ return nil if error_body.empty?
127
+
128
+ JSON.parse(error_body)
129
+ rescue JSON::ParserError
130
+ error_body
131
+ end
132
+
133
+ private
134
+
135
+ def write(chunk, env)
136
+ return @error_body << chunk unless env.nil? || (200..299).cover?(env.status)
137
+
138
+ @io.write(chunk)
139
+ @written += chunk.bytesize
140
+ end
141
+ end
142
+ private_constant :StreamSink
143
+
91
144
  def inspect
92
- "#<#{self.class.name} url=#{url.inspect} workspace_id=#{workspace_id.inspect}\
93
- api_key=#{api_key.nil? ? "nil" : "[FILTERED]"}>"
145
+ "#<#{self.class.name} url=#{url.inspect} workspace_id=#{workspace_id.inspect} " \
146
+ "api_key=#{api_key.nil? ? "nil" : "[FILTERED]"}>"
94
147
  end
95
148
 
96
149
  private
97
150
 
98
- # Databasus takes the token as a bearer credential; both a user JWT and a
99
- # verification-agent token go in the same header.
151
+ def prepare(req, params: nil, headers: nil)
152
+ req.params.update(stringify(params)) if params
153
+ req.headers.update(request_headers(headers))
154
+ end
155
+
100
156
  def request_headers(extra)
101
157
  headers = {}
102
158
  headers["Authorization"] = "Bearer #{api_key}" unless api_key.nil? || api_key.to_s.empty?
@@ -110,13 +166,12 @@ module DatabasusRuby
110
166
  url.to_s.end_with?("/") ? url.to_s : "#{url}/"
111
167
  end
112
168
 
169
+ # Drops nils; arrays stay arrays so FlatParamsEncoder repeats the key.
113
170
  def stringify(params)
114
171
  params.each_with_object({}) do |(key, value), acc|
115
172
  next if value.nil?
116
173
 
117
- # The only array-valued query parameter in the API (backups status)
118
- # is declared as collectionFormat: csv.
119
- acc[key.to_s] = value.is_a?(::Array) ? value.join(",") : value
174
+ acc[key.to_s] = value.is_a?(::Array) ? value.map(&:to_s) : value
120
175
  end
121
176
  end
122
177
  end
@@ -3,38 +3,44 @@
3
3
  module DatabasusRuby
4
4
  # Uniform return value for every list endpoint.
5
5
  #
6
- # Databasus answers lists in three different shapes: a bare array
7
- # (GET /databases), a paginated envelope (GET /backups) and an unpaginated
8
- # envelope (GET /workspaces). Collection flattens all three into an
9
- # Enumerable that always answers #total, and answers #limit / #offset when
10
- # the endpoint actually paginates.
6
+ # Databasus answers every list as a cursor page: `{ items: [...],
7
+ # nextCursor: "..." }`, plus page-level extras on some endpoints (the logical
8
+ # backups page carries `totalUsageMb`). Pass #next_cursor back as `after:` to
9
+ # read the next page; it is nil on the last one.
10
+ #
11
+ # page = client.backups.list(database_id: id, limit: 50)
12
+ # page.extras.total_usage_mb
13
+ # page.next_cursor
11
14
  class Collection
12
15
  include Enumerable
13
16
 
14
- attr_reader :data, :total, :limit, :offset
17
+ attr_reader :data, :next_cursor, :extras
15
18
 
16
- def initialize(data: [], total: nil, limit: nil, offset: nil)
17
- @data = data.map { |item| item.is_a?(Object) ? item : Object.new(item) }
18
- @total = total || @data.size
19
- @limit = limit
20
- @offset = offset
19
+ # `wrap` builds one entry from its raw hash; it defaults to a plain Object
20
+ # and endpoints pass a Record builder instead.
21
+ def initialize(data: [], next_cursor: nil, extras: {}, wrap: nil)
22
+ @data = data.map { |item| item.is_a?(Object) ? item : (wrap || Object.method(:new)).call(item) }
23
+ @next_cursor = next_cursor
24
+ @extras = extras.is_a?(Object) ? extras : Object.new(extras)
21
25
  end
22
26
 
23
- # `key` names the array inside an envelope response and is ignored for
24
- # bare-array responses.
25
- def self.from(body, key:)
27
+ PAGE_KEYS = %w[items nextCursor].freeze
28
+
29
+ def self.from(body, wrap: nil)
26
30
  case body
27
- when ::Array
28
- new(data: body)
29
- when Hash
30
- new(data: body[key] || [], total: body["total"], limit: body["limit"], offset: body["offset"])
31
- when nil
32
- new
33
- else
34
- raise Error, "unexpected list payload: #{body.class}"
31
+ when Hash then from_page(body, wrap)
32
+ when ::Array then new(data: body, wrap: wrap)
33
+ when nil, "" then new
34
+ else raise Error, "unexpected list payload: #{body.class}"
35
35
  end
36
36
  end
37
37
 
38
+ def self.from_page(page, wrap)
39
+ extras = page.reject { |key, _| PAGE_KEYS.include?(key.to_s) }
40
+ new(data: page["items"] || [], next_cursor: page["nextCursor"], extras: extras, wrap: wrap)
41
+ end
42
+ private_class_method :from_page
43
+
38
44
  def each(&block)
39
45
  return data.each unless block
40
46
 
@@ -59,25 +65,12 @@ module DatabasusRuby
59
65
  data.last
60
66
  end
61
67
 
62
- def paginated?
63
- !limit.nil?
64
- end
65
-
66
- # Offset to pass to the next call, or nil when this is the last page (or
67
- # the endpoint does not paginate at all).
68
- def next_offset
69
- return nil unless paginated?
70
-
71
- following = (offset || 0) + data.size
72
- following < total ? following : nil
73
- end
74
-
75
68
  def last_page?
76
- next_offset.nil?
69
+ next_cursor.nil? || next_cursor.to_s.empty?
77
70
  end
78
71
 
79
72
  def inspect
80
- "#<#{self.class.name} size=#{size} total=#{total} limit=#{limit.inspect} offset=#{offset.inspect}>"
73
+ "#<#{self.class.name} size=#{size} next_cursor=#{next_cursor.inspect}>"
81
74
  end
82
75
  end
83
76
  end
@@ -43,14 +43,21 @@ module DatabasusRuby
43
43
  client.request(:delete, path, params: params)
44
44
  end
45
45
 
46
- # Wraps a single-object response.
47
- def object(response)
48
- Object.new(response.body.is_a?(Hash) ? response.body : {})
46
+ # Wraps a single-object response. `as` names a Record subclass for the
47
+ # domains whose payloads carry methods of their own.
48
+ def object(response, as: Object)
49
+ build(as, response.body.is_a?(Hash) ? response.body : {})
49
50
  end
50
51
 
51
- # Wraps a list response; `key` names the array inside an envelope payload.
52
- def collection(response, key:)
53
- Collection.from(response.body, key: key)
52
+ # Wraps a cursor-page list response.
53
+ def collection(response, as: Object)
54
+ Collection.from(response.body, wrap: ->(item) { build(as, item) })
55
+ end
56
+
57
+ # Records need the client to reach their related endpoints; plain payload
58
+ # objects do not.
59
+ def build(klass, attributes)
60
+ klass <= Record ? klass.new(client, attributes) : klass.new(attributes)
54
61
  end
55
62
 
56
63
  # For endpoints that answer 200/204 with no meaningful body.
@@ -58,13 +65,6 @@ module DatabasusRuby
58
65
  true
59
66
  end
60
67
 
61
- # Endpoints answering a single-entry map whose key is not documented in the
62
- # swagger schema, e.g. GET /databases/notifier/{id}/is-using.
63
- def single_value(response)
64
- body = response.body
65
- body.is_a?(Hash) ? body.values.first : body
66
- end
67
-
68
68
  # Request bodies are built from keyword arguments, and the API distinguishes
69
69
  # an absent key from a null one in a few places, so nils are dropped.
70
70
  def compact(body)
@@ -5,24 +5,44 @@ module DatabasusRuby
5
5
  class Error < StandardError; end
6
6
 
7
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, ...).
8
+ # the API at all (a workspace id, a url, ...).
9
9
  class ConfigurationError < Error; end
10
10
 
11
+ # Raised by Auth#signin when the account requires an emailed sign-in code
12
+ # (two-factor). An unattended client cannot answer it; use an account
13
+ # without 2FA or finish with Auth#verify_sign_in_code.
14
+ class SignInCodeRequiredError < Error
15
+ attr_reader :pending_sign_in_id, :email
16
+
17
+ def initialize(pending_sign_in_id:, email:)
18
+ @pending_sign_in_id = pending_sign_in_id
19
+ @email = email
20
+ super("sign-in for #{email} needs the emailed code (pending sign-in #{pending_sign_in_id})")
21
+ end
22
+ end
23
+
11
24
  # Raised when Databasus answers with a non-successful status code.
25
+ #
26
+ # Databasus answers errors as RFC 9457 problems:
27
+ # `{ type, title, status, detail, code, errors }`. `code` is the stable
28
+ # machine-readable reason (`database_connection_failed`,
29
+ # `insufficient_disk_space`, `already_running`, ...).
12
30
  class APIError < Error
13
- attr_reader :status, :body, :response
31
+ attr_reader :status, :body, :response, :code
14
32
 
15
- def initialize(message = nil, status: nil, body: nil, response: nil)
33
+ def initialize(message = nil, status: nil, body: nil, response: nil, code: nil)
16
34
  @status = status
17
35
  @body = body
18
36
  @response = response
37
+ @code = code
19
38
  super(message || "Databasus API returned #{status}")
20
39
  end
21
40
 
22
41
  # Maps a Faraday response onto the most specific error class available.
23
- def self.from_response(response)
42
+ # `body` overrides the response body (streamed responses carry none).
43
+ def self.from_response(response, body: response.body)
24
44
  klass = for_status(response.status)
25
- klass.new(message_from(response.body), status: response.status, body: response.body, response: response)
45
+ klass.new(message_from(body), status: response.status, body: body, response: response, code: code_from(body))
26
46
  end
27
47
 
28
48
  def self.for_status(status)
@@ -31,15 +51,25 @@ module DatabasusRuby
31
51
  STATUS_ERRORS.fetch(status, APIError)
32
52
  end
33
53
 
34
- # Every handler in the Go backend answers errors as {"error": "..."}, but
35
- # streaming endpoints can still fail with a plain-text body.
54
+ # A validation problem's `detail` is generic ("the request breaks a
55
+ # validation rule"); the field errors are what say which rule.
36
56
  def self.message_from(body)
37
57
  case body
38
- when Hash then body["error"] || body["message"] || body[:error]
58
+ when Hash then problem_message(body)
39
59
  when String then body.empty? ? nil : body[0, 500]
40
60
  end
41
61
  end
42
- private_class_method :message_from
62
+
63
+ def self.problem_message(problem)
64
+ message = problem["detail"] || problem["title"] || problem["error"] || problem["message"]
65
+ fields = Array(problem["errors"]).filter_map { |e| "#{e["field"]}: #{e["detail"]}" if e.is_a?(Hash) }
66
+ fields.empty? ? message : [message, fields.join("; ")].compact.join(" — ")
67
+ end
68
+
69
+ def self.code_from(body)
70
+ body["code"] if body.is_a?(Hash)
71
+ end
72
+ private_class_method :message_from, :problem_message, :code_from
43
73
  end
44
74
 
45
75
  class BadRequestError < APIError; end
@@ -1,28 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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`.
4
+ # Obtaining a session token. Every other endpoint needs the token these
5
+ # methods return, so a completed sign-in installs it on the client as
6
+ # `api_key`.
6
7
  #
7
8
  # client.auth.signin(email: "me@example.com", password: "secret")
8
- # client.users.me.name
9
+ # client.databases.list(workspace_id: id)
9
10
  #
10
- # Read the token off the result if you want to keep it for a later client.
11
+ # An account with two-factor sign-in answers CODE_REQUIRED: #signin raises
12
+ # SignInCodeRequiredError and #verify_sign_in_code finishes it.
11
13
  class Auth < Object
12
14
  include Endpoint
13
15
 
14
- # POST /users/signin
16
+ # POST /sessions
17
+ #
18
+ # @return [DatabasusRuby::Object] the completed sign-in (`token`, `userId`, `email`)
19
+ # @raise [DatabasusRuby::SignInCodeRequiredError] when the account needs the emailed code
15
20
  def signin(email:, password:, cloudflare_turnstile_token: nil)
16
- authorize object(http_post("users/signin", {
21
+ outcome = object(http_post("sessions", {
17
22
  email: email,
18
23
  password: password,
19
24
  cloudflareTurnstileToken: cloudflare_turnstile_token
20
25
  }))
26
+ if outcome.outcome == "CODE_REQUIRED"
27
+ raise SignInCodeRequiredError.new(pending_sign_in_id: outcome.pending_sign_in_id, email: outcome.email)
28
+ end
29
+
30
+ authorize outcome
31
+ end
32
+
33
+ # POST /pending-sign-ins/{id}/verify
34
+ def verify_sign_in_code(pending_sign_in_id, code:, cloudflare_turnstile_token: nil)
35
+ authorize object(http_post("pending-sign-ins/#{escape(pending_sign_in_id)}/verify", {
36
+ code: code,
37
+ cloudflareTurnstileToken: cloudflare_turnstile_token
38
+ }))
21
39
  end
22
40
 
23
- # POST /users/signup
41
+ # POST /users — self-registration, when the instance allows it.
24
42
  def signup(email:, name:, password:, cloudflare_turnstile_token: nil)
25
- authorize object(http_post("users/signup", {
43
+ authorize object(http_post("users", {
26
44
  email: email,
27
45
  name: name,
28
46
  password: password,
@@ -30,27 +48,14 @@ module DatabasusRuby
30
48
  }))
31
49
  end
32
50
 
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
- }))
51
+ # POST /password-resets — emails a reset code.
52
+ def request_password_reset(email:, cloudflare_turnstile_token: nil)
53
+ acknowledged(http_post("password-resets", { email: email, cloudflareTurnstileToken: cloudflare_turnstile_token }))
49
54
  end
50
55
 
51
- # POST /users/reset-password
56
+ # POST /password-reset-completions
52
57
  def reset_password(email:, code:, new_password:)
53
- object(http_post("users/reset-password", { email: email, code: code, newPassword: new_password }))
58
+ acknowledged(http_post("password-reset-completions", { email: email, code: code, newPassword: new_password }))
54
59
  end
55
60
 
56
61
  private