pocketbase 0.1.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.
@@ -0,0 +1,367 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "thread"
5
+ require "timeout"
6
+
7
+ module PocketBase
8
+ module Services
9
+ # Record/collection-auth service (port of js-sdk RecordService).
10
+ class RecordService < CrudService
11
+ attr_reader :collection_id_or_name
12
+
13
+ def initialize(client, collection_id_or_name)
14
+ super(client)
15
+ @collection_id_or_name = collection_id_or_name.to_s
16
+ end
17
+
18
+ def base_collection_path
19
+ "/api/collections/#{Utils.encode_uri_component(@collection_id_or_name)}"
20
+ end
21
+
22
+ def base_crud_path
23
+ "#{base_collection_path}/records"
24
+ end
25
+
26
+ # Whether the service collection is the superusers collection.
27
+ def superusers?
28
+ @collection_id_or_name == "_superusers" || @collection_id_or_name == "_pbc_2773867675"
29
+ end
30
+
31
+ # --- Realtime ------------------------------------------------------
32
+
33
+ # Subscribes to realtime changes of a topic ("*" or a record id).
34
+ # Returns an unsubscribe lambda.
35
+ #
36
+ # unsub = pb.collection("posts").subscribe("*") { |e| puts e["action"] }
37
+ # unsub.call
38
+ def subscribe(topic, options = {}, &callback)
39
+ raise ArgumentError, "topic must be set" if topic.nil? || topic.to_s.empty?
40
+ raise ArgumentError, "callback block required" unless callback
41
+
42
+ @client.realtime.subscribe("#{@collection_id_or_name}/#{topic}", options, &callback)
43
+ end
44
+
45
+ # Unsubscribes from a topic, or from everything of this collection
46
+ # when no topic is given.
47
+ def unsubscribe(topic = nil)
48
+ if topic
49
+ @client.realtime.unsubscribe("#{@collection_id_or_name}/#{topic}")
50
+ else
51
+ @client.realtime.unsubscribe_by_prefix(@collection_id_or_name)
52
+ end
53
+ end
54
+
55
+ # --- CRUD overrides (auth store syncing) ----------------------------
56
+
57
+ def update(id, body_params = nil, options = {})
58
+ item = super
59
+ store_record = @client.auth_store.record
60
+ if store_record.is_a?(Hash) && record_id(store_record) == item_id(item) &&
61
+ auth_record_collection?(store_record)
62
+ auth_expand = ((store_record["expand"] || store_record[:expand]) || {}).dup
63
+ auth_record = store_record.merge(item)
64
+ merged_expand = auth_expand.merge(item["expand"] || item[:expand] || {})
65
+ auth_record["expand"] = merged_expand unless merged_expand.empty? && auth_expand.empty?
66
+ @client.auth_store.save(@client.auth_store.token, auth_record)
67
+ end
68
+ item
69
+ end
70
+
71
+ def delete(id, options = {})
72
+ success = super
73
+ store_record = @client.auth_store.record
74
+ if success && store_record.is_a?(Hash) && record_id(store_record) == id.to_s &&
75
+ auth_record_collection?(store_record)
76
+ @client.auth_store.clear
77
+ end
78
+ success
79
+ end
80
+
81
+ # --- Auth -----------------------------------------------------------
82
+
83
+ # Returns all available collection auth methods.
84
+ def list_auth_methods(options = {})
85
+ opts = send_options({ method: "GET", query: { "fields" => "mfa,otp,password,oauth2" } }, options)
86
+ @client.send_request("#{base_collection_path}/auth-methods", opts)
87
+ end
88
+
89
+ # Authenticates with username/email + password. On success the client
90
+ # auth store is updated and a Hash with token, record, and meta is returned.
91
+ #
92
+ # For the _superusers collection you may pass
93
+ # `auto_refresh_threshold:` (seconds) to auto-refresh the token.
94
+ def auth_with_password(username_or_email, password, options = {})
95
+ auto_refresh_threshold = options.delete(:auto_refresh_threshold)
96
+
97
+ if superusers?
98
+ @client.reset_auto_refresh unless options[:autoRefresh] || options["autoRefresh"]
99
+ end
100
+
101
+ opts = send_options(
102
+ { method: "POST", body: { "identity" => username_or_email, "password" => password } },
103
+ options
104
+ )
105
+ auth_data = @client.send_request("#{base_collection_path}/auth-with-password", opts)
106
+ auth_data = auth_response(auth_data)
107
+
108
+ if auto_refresh_threshold && superusers?
109
+ @client.register_auto_refresh(
110
+ auto_refresh_threshold,
111
+ -> { auth_refresh(autoRefresh: true) },
112
+ -> { auth_with_password(username_or_email, password, options.merge(autoRefresh: true)) }
113
+ )
114
+ end
115
+
116
+ auth_data
117
+ end
118
+
119
+ # Authenticates with an OAuth2 code (see also #auth_with_oauth2 for the
120
+ # assisted flow and #oauth2_login_url for manual flows).
121
+ def auth_with_oauth2_code(provider, code, code_verifier, redirect_url, create_data = nil, options = {})
122
+ opts = send_options(
123
+ {
124
+ method: "POST",
125
+ body: {
126
+ "provider" => provider.to_s,
127
+ "code" => code,
128
+ "codeVerifier" => code_verifier,
129
+ "redirectURL" => redirect_url,
130
+ "createData" => create_data,
131
+ },
132
+ },
133
+ options
134
+ )
135
+ auth_response(@client.send_request("#{base_collection_path}/auth-with-oauth2", opts))
136
+ end
137
+
138
+ # Assisted OAuth2 sign-in: subscribes to the realtime @oauth2 channel,
139
+ # yields the provider sign-in URL to the given block (open it in a
140
+ # browser), waits for the provider redirect, then exchanges the code.
141
+ #
142
+ # auth_data = pb.collection("users").auth_with_oauth2(provider: "google") do |url|
143
+ # puts "Open this URL: #{url}"
144
+ # end
145
+ #
146
+ # Configure `https://yourdomain.com/api/oauth2-redirect` as the redirect
147
+ # URL in the provider dashboard.
148
+ def auth_with_oauth2(provider:, scopes: nil, create_data: nil, redirect_url: nil,
149
+ timeout: 300, **options, &url_callback)
150
+ raise ArgumentError, "a url_callback block is required" unless url_callback
151
+
152
+ info = find_oauth2_provider(provider)
153
+ redirect_url ||= @client.build_url("/api/oauth2-redirect")
154
+
155
+ queue = Queue.new
156
+ realtime = @client.realtime
157
+ unsub = realtime.subscribe("@oauth2") { |e| queue << e }
158
+
159
+ begin
160
+ replacements = { "state" => realtime.client_id }
161
+ replacements["scope"] = Array(scopes).join(" ") if scopes && !Array(scopes).empty?
162
+ url = Utils.replace_query_params(info["authURL"].to_s + redirect_url.to_s, replacements)
163
+ url_callback.call(url)
164
+
165
+ event = Timeout.timeout(timeout) { queue.pop }
166
+ current_state = realtime.client_id
167
+ if event["state"].nil? || event["state"] != current_state
168
+ raise ClientResponseError.new("message" => "State parameters don't match.")
169
+ end
170
+ if event["error"] || !event["code"]
171
+ raise ClientResponseError.new("message" => "OAuth2 redirect error or missing code: #{event["error"]}")
172
+ end
173
+
174
+ auth_with_oauth2_code(provider.to_s, event["code"], info["codeVerifier"],
175
+ redirect_url, create_data, options)
176
+ ensure
177
+ unsub.call
178
+ end
179
+ rescue Timeout::Error
180
+ raise ClientResponseError.new("message" => "OAuth2 flow timed out waiting for the provider redirect.")
181
+ end
182
+
183
+ # Builds an OAuth2 sign-in URL for manual flows. Returns a Hash with
184
+ # "url", "state" and "code_verifier" — pass the code_verifier to
185
+ # #auth_with_oauth2_code once you have the provider code.
186
+ def oauth2_login_url(provider, redirect_url: nil, scopes: nil)
187
+ info = find_oauth2_provider(provider)
188
+ redirect_url ||= @client.build_url("/api/oauth2-redirect")
189
+ state = SecureRandom.hex(16)
190
+ replacements = { "state" => state }
191
+ replacements["scope"] = Array(scopes).join(" ") if scopes && !Array(scopes).empty?
192
+ {
193
+ "url" => Utils.replace_query_params(info["authURL"].to_s + redirect_url.to_s, replacements),
194
+ "state" => state,
195
+ "code_verifier" => info["codeVerifier"],
196
+ }
197
+ end
198
+
199
+ # Refreshes the current authenticated record, returning a new token.
200
+ def auth_refresh(options = {})
201
+ opts = send_options({ method: "POST" }, options)
202
+ auth_response(@client.send_request("#{base_collection_path}/auth-refresh", opts))
203
+ end
204
+
205
+ # Sends a password reset email. Returns true on success.
206
+ def request_password_reset(email, options = {})
207
+ opts = send_options({ method: "POST", body: { "email" => email } }, options)
208
+ @client.send_request("#{base_collection_path}/request-password-reset", opts)
209
+ true
210
+ end
211
+
212
+ # Confirms a password reset. Returns true on success.
213
+ def confirm_password_reset(password_reset_token, password, password_confirm, options = {})
214
+ opts = send_options(
215
+ { method: "POST",
216
+ body: { "token" => password_reset_token, "password" => password,
217
+ "passwordConfirm" => password_confirm } },
218
+ options
219
+ )
220
+ @client.send_request("#{base_collection_path}/confirm-password-reset", opts)
221
+ true
222
+ end
223
+
224
+ # Sends a verification email. Returns true on success.
225
+ def request_verification(email, options = {})
226
+ opts = send_options({ method: "POST", body: { "email" => email } }, options)
227
+ @client.send_request("#{base_collection_path}/request-verification", opts)
228
+ true
229
+ end
230
+
231
+ # Confirms an email verification. Updates the auth store verified flag
232
+ # when it matches the current auth record. Returns true on success.
233
+ def confirm_verification(verification_token, options = {})
234
+ opts = send_options({ method: "POST", body: { "token" => verification_token } }, options)
235
+ @client.send_request("#{base_collection_path}/confirm-verification", opts)
236
+
237
+ payload = Utils.token_payload(verification_token)
238
+ model = @client.auth_store.record
239
+ if model.is_a?(Hash) && !verified?(model) &&
240
+ record_id(model) == payload["id"] && record_collection_id(model) == payload["collectionId"]
241
+ model = model.merge("verified" => true)
242
+ @client.auth_store.save(@client.auth_store.token, model)
243
+ end
244
+ true
245
+ end
246
+
247
+ # Sends an email change request to the authenticated record.
248
+ def request_email_change(new_email, options = {})
249
+ opts = send_options({ method: "POST", body: { "newEmail" => new_email } }, options)
250
+ @client.send_request("#{base_collection_path}/request-email-change", opts)
251
+ true
252
+ end
253
+
254
+ # Confirms a new email address. Clears the auth store when it matches
255
+ # the current auth record. Returns true on success.
256
+ def confirm_email_change(email_change_token, password, options = {})
257
+ opts = send_options(
258
+ { method: "POST", body: { "token" => email_change_token, "password" => password } },
259
+ options
260
+ )
261
+ @client.send_request("#{base_collection_path}/confirm-email-change", opts)
262
+
263
+ payload = Utils.token_payload(email_change_token)
264
+ model = @client.auth_store.record
265
+ if model.is_a?(Hash) && record_id(model) == payload["id"] &&
266
+ record_collection_id(model) == payload["collectionId"]
267
+ @client.auth_store.clear
268
+ end
269
+ true
270
+ end
271
+
272
+ # Sends an OTP to the given email. Returns { "otpId" => ... }.
273
+ def request_otp(email, options = {})
274
+ opts = send_options({ method: "POST", body: { "email" => email } }, options)
275
+ @client.send_request("#{base_collection_path}/request-otp", opts)
276
+ end
277
+
278
+ # Authenticates with an OTP id + password.
279
+ def auth_with_otp(otp_id, password, options = {})
280
+ opts = send_options(
281
+ { method: "POST", body: { "otpId" => otp_id, "password" => password } },
282
+ options
283
+ )
284
+ auth_response(@client.send_request("#{base_collection_path}/auth-with-otp", opts))
285
+ end
286
+
287
+ # Impersonates a record (superusers only). Returns a NEW client loaded
288
+ # with the impersonated auth state.
289
+ def impersonate(record_id, duration = 0, options = {})
290
+ opts = send_options({ method: "POST", body: { "duration" => duration } }, options)
291
+ headers = (opts[:headers] || {}).dup
292
+ headers["Authorization"] ||= @client.auth_store.token
293
+ opts[:headers] = headers
294
+
295
+ client = Client.new(@client.base_url, lang: @client.lang)
296
+ auth_data = client.send_request(
297
+ "#{base_collection_path}/impersonate/#{Utils.encode_uri_component(record_id.to_s)}", opts
298
+ )
299
+ client.auth_store.save(auth_data["token"], decode(auth_data["record"] || {}))
300
+ client
301
+ end
302
+
303
+ # @deprecated Use collection("_externalAuths") directly.
304
+ def list_external_auths(record_id, options = {})
305
+ @client.collection("_externalAuths").get_full_list(
306
+ options.merge(filter: @client.filter("recordRef = {:id}", id: record_id))
307
+ )
308
+ end
309
+
310
+ # @deprecated Use collection("_externalAuths") directly.
311
+ def unlink_external_auth(record_id, provider, options = {})
312
+ ea = @client.collection("_externalAuths").get_first_list_item(
313
+ @client.filter("recordRef = {:recordId} && provider = {:provider}",
314
+ recordId: record_id, provider: provider)
315
+ )
316
+ @client.collection("_externalAuths").delete(ea["id"], options)
317
+ true
318
+ end
319
+
320
+ private
321
+
322
+ def auth_response(response_data)
323
+ record = decode(response_data["record"] || response_data[:record] || {})
324
+ @client.auth_store.save(response_data["token"] || response_data[:token], record)
325
+ (response_data || {}).merge("token" => response_data["token"] || response_data[:token] || "",
326
+ "record" => record)
327
+ end
328
+
329
+ def find_oauth2_provider(provider)
330
+ methods = list_auth_methods
331
+ info = methods.dig("oauth2", "providers")&.find { |p| p["name"] == provider.to_s }
332
+ unless info
333
+ raise ClientResponseError.new("message" => %(Missing or invalid provider "#{provider}".))
334
+ end
335
+
336
+ info
337
+ end
338
+
339
+ def auth_record_collection?(store_record)
340
+ [store_record["collectionId"], store_record["collectionName"],
341
+ store_record[:collectionId], store_record[:collectionName]]
342
+ .compact.map(&:to_s).include?(@collection_id_or_name)
343
+ end
344
+
345
+ def record_id(model)
346
+ (model["id"] || model[:id]).to_s
347
+ end
348
+
349
+ def item_id(item)
350
+ ((item["id"] || item[:id]) || "").to_s
351
+ end
352
+
353
+ def record_collection(model)
354
+ model["collectionName"] || model[:collectionName] ||
355
+ model["collectionId"] || model[:collectionId]
356
+ end
357
+
358
+ def record_collection_id(model)
359
+ model["collectionId"] || model[:collectionId]
360
+ end
361
+
362
+ def verified?(model)
363
+ model["verified"] || model[:verified]
364
+ end
365
+ end
366
+ end
367
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PocketBase
4
+ module Services
5
+ # Settings service (port of js-sdk SettingsService).
6
+ class SettingsService < BaseService
7
+ # Fetches all available app settings.
8
+ def get_all(options = {})
9
+ opts = send_options({ method: "GET" }, options)
10
+ @client.send_request("/api/settings", opts)
11
+ end
12
+
13
+ # Bulk updates app settings.
14
+ def update(body_params = nil, options = {})
15
+ opts = send_options({ method: "PATCH", body: body_params }, options)
16
+ @client.send_request("/api/settings", opts)
17
+ end
18
+
19
+ # Performs an S3 filesystem connection test ("storage" or "backups").
20
+ def test_s3(filesystem = "storage", options = {})
21
+ opts = send_options({ method: "POST", body: { "filesystem" => filesystem } }, options)
22
+ @client.send_request("/api/settings/test/s3", opts)
23
+ true
24
+ end
25
+
26
+ # Sends a test email (templates: verification, password-reset, email-change).
27
+ def test_email(collection_id_or_name, to_email, email_template, options = {})
28
+ opts = send_options(
29
+ { method: "POST",
30
+ body: { "email" => to_email, "template" => email_template,
31
+ "collection" => collection_id_or_name } },
32
+ options
33
+ )
34
+ @client.send_request("/api/settings/test/email", opts)
35
+ true
36
+ end
37
+
38
+ # Generates a new Apple OAuth2 client secret.
39
+ def generate_apple_client_secret(client_id, team_id, key_id, private_key, duration, options = {})
40
+ opts = send_options(
41
+ { method: "POST",
42
+ body: { "clientId" => client_id, "teamId" => team_id, "keyId" => key_id,
43
+ "privateKey" => private_key, "duration" => duration } },
44
+ options
45
+ )
46
+ @client.send_request("/api/settings/apple/generate-client-secret", opts)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PocketBase
4
+ module Services
5
+ # SQL service (port of js-sdk SQLService). Superusers only.
6
+ class SQLService < BaseService
7
+ # Executes a raw SQL query.
8
+ def run(query, options = {})
9
+ opts = send_options({ method: "POST", body: { "query" => query } }, options)
10
+ @client.send_request("/api/sql", opts)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "cgi"
5
+ require "json"
6
+ require "time"
7
+ require "date"
8
+
9
+ module PocketBase
10
+ # Shared helpers mirroring the js-sdk tools (jwt, options, filter).
11
+ module Utils
12
+ module_function
13
+
14
+ # Matches JavaScript's encodeURIComponent (does NOT escape A-Za-z0-9 -_.!~*'()).
15
+ def encode_uri_component(str)
16
+ str.to_s.gsub(/[^A-Za-z0-9\-_.!~*'()]/) do |c|
17
+ c.bytes.map { |b| format("%%%02X", b) }.join
18
+ end
19
+ end
20
+
21
+ def decode_uri_component(str)
22
+ CGI.unescape(str.to_s.gsub("+", "%2B"))
23
+ end
24
+
25
+ # --- JWT ---------------------------------------------------------------
26
+
27
+ # Returns the payload of a JWT token as a Hash ({} when invalid).
28
+ def token_payload(token)
29
+ return {} if token.nil? || token.to_s.empty?
30
+
31
+ segment = token.to_s.split(".")[1]
32
+ return {} if segment.nil? || segment.empty?
33
+
34
+ padded = segment + ("=" * ((4 - (segment.length % 4)) % 4))
35
+ JSON.parse(Base64.urlsafe_decode64(padded)) || {}
36
+ rescue StandardError
37
+ {}
38
+ end
39
+
40
+ # Checks whether a JWT token is expired.
41
+ # Tokens without an `exp` claim are considered valid;
42
+ # empty/invalid tokens are considered expired.
43
+ # `threshold` is subtracted from `exp` (in seconds).
44
+ def token_expired?(token, threshold = 0)
45
+ payload = token_payload(token)
46
+ return true if payload.empty?
47
+
48
+ exp = payload["exp"]
49
+ return false if exp.nil?
50
+
51
+ exp.to_f - threshold <= Time.now.to_f
52
+ end
53
+
54
+ # --- Query params (mirrors js-sdk serializeQueryParams) -----------------
55
+
56
+ def serialize_query_params(params)
57
+ parts = []
58
+ (params || {}).each do |key, value|
59
+ values = value.is_a?(Array) ? value : [value]
60
+ values.each do |v|
61
+ v = prepare_query_param_value(v)
62
+ next if v.nil?
63
+
64
+ parts << "#{encode_uri_component(key.to_s)}=#{v}"
65
+ end
66
+ end
67
+ parts.join("&")
68
+ end
69
+
70
+ def prepare_query_param_value(value)
71
+ return nil if value.nil?
72
+
73
+ case value
74
+ when Time, DateTime
75
+ encode_uri_component(value.utc.iso8601(3).sub("T", " "))
76
+ when Date
77
+ encode_uri_component(value.strftime("%Y-%m-%d"))
78
+ when Hash, Array
79
+ encode_uri_component(JSON.generate(value))
80
+ when TrueClass, FalseClass, Numeric
81
+ encode_uri_component(value.to_s)
82
+ else
83
+ encode_uri_component(value.to_s)
84
+ end
85
+ end
86
+
87
+ # --- Filter builder (mirrors js-sdk Client#filter) ---------------------
88
+
89
+ # Constructs a filter expression with placeholders populated from params.
90
+ #
91
+ # Placeholders use the `{:paramName}` notation:
92
+ # pb.filter('title ~ {:title} && created >= {:created}',
93
+ # title: "hello", created: Time.now)
94
+ def build_filter(raw, params = nil)
95
+ return raw.to_s if params.nil? || params.empty?
96
+
97
+ mapping = {}
98
+ params.each { |k, v| mapping["{:#{k}}"] = stringify_filter_param(v) }
99
+ return raw.to_s if mapping.empty?
100
+
101
+ pattern = Regexp.union(mapping.keys)
102
+ raw.to_s.gsub(pattern) { |m| mapping[m] }
103
+ end
104
+
105
+ def stringify_filter_param(val)
106
+ case val
107
+ when TrueClass, FalseClass, Numeric
108
+ val.to_s
109
+ when String
110
+ JSON.generate(val)
111
+ when Time, DateTime
112
+ JSON.generate(val.utc.iso8601(3).sub("T", " "))
113
+ when Date
114
+ JSON.generate(val.strftime("%Y-%m-%d"))
115
+ when NilClass
116
+ "null"
117
+ else
118
+ s = JSON.generate(val)
119
+ s = JSON.generate(s) if s.start_with?("[", "{")
120
+ s
121
+ end
122
+ end
123
+
124
+ # Very small URL query-string replacement helper (mirrors the js-sdk
125
+ # private _replaceQueryParams used for the OAuth2 auth URL).
126
+ def replace_query_params(url, replacements = {})
127
+ path, query = url.to_s.split("?", 2)
128
+ parsed = {}
129
+ (query || "").split("&").each do |param|
130
+ next if param.empty?
131
+
132
+ k, v = param.split("=", 2)
133
+ parsed[decode_uri_component(k)] = decode_uri_component(v || "")
134
+ end
135
+ replacements.each do |k, v|
136
+ if v.nil?
137
+ parsed.delete(k.to_s)
138
+ else
139
+ parsed[k.to_s] = v.to_s
140
+ end
141
+ end
142
+ rebuilt = parsed.map { |k, v| "#{encode_uri_component(k)}=#{encode_uri_component(v)}" }.join("&")
143
+ rebuilt.empty? ? path : "#{path}?#{rebuilt}"
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PocketBase
4
+ VERSION = "0.1.1"
5
+ end
data/lib/pocketbase.rb ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pocketbase/version"
4
+ require_relative "pocketbase/utils"
5
+ require_relative "pocketbase/client_response_error"
6
+ require_relative "pocketbase/multipart"
7
+ require_relative "pocketbase/auth_store"
8
+ require_relative "pocketbase/services/base_service"
9
+ require_relative "pocketbase/services/crud_service"
10
+ require_relative "pocketbase/services/realtime_service"
11
+ require_relative "pocketbase/services/record_service"
12
+ require_relative "pocketbase/services/collection_service"
13
+ require_relative "pocketbase/services/file_service"
14
+ require_relative "pocketbase/services/log_service"
15
+ require_relative "pocketbase/services/settings_service"
16
+ require_relative "pocketbase/services/backup_service"
17
+ require_relative "pocketbase/services/health_service"
18
+ require_relative "pocketbase/services/cron_service"
19
+ require_relative "pocketbase/services/sql_service"
20
+ require_relative "pocketbase/services/batch_service"
21
+ require_relative "pocketbase/client"
22
+
23
+ # Ruby SDK for PocketBase (port of the official pocketbase/js-sdk).
24
+ #
25
+ # require "pocketbase"
26
+ #
27
+ # pb = PocketBase.new("http://127.0.0.1:8090")
28
+ # pb.collection("users").auth_with_password("test@example.com", "1234567890")
29
+ # pb.collection("posts").get_list(1, 30, filter: "published = true")
30
+ module PocketBase
31
+ # Shortcut for PocketBase::Client.new
32
+ def self.new(base_url = "/", auth_store: nil, lang: "en-US")
33
+ Client.new(base_url, auth_store: auth_store, lang: lang)
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pocketbase
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Azmi Muwahid
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: 'A Ruby port of the official PocketBase JavaScript SDK (pocketbase/js-sdk):
13
+ record CRUD, auth (password, OAuth2, OTP), file uploads, realtime subscriptions
14
+ over SSE, backups, settings, logs, cron jobs, SQL and batch requests. Zero runtime
15
+ dependencies (stdlib only).'
16
+ email:
17
+ - azmimuwahid@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE
24
+ - README.md
25
+ - lib/pocketbase.rb
26
+ - lib/pocketbase/auth_store.rb
27
+ - lib/pocketbase/client.rb
28
+ - lib/pocketbase/client_response_error.rb
29
+ - lib/pocketbase/multipart.rb
30
+ - lib/pocketbase/services/backup_service.rb
31
+ - lib/pocketbase/services/base_service.rb
32
+ - lib/pocketbase/services/batch_service.rb
33
+ - lib/pocketbase/services/collection_service.rb
34
+ - lib/pocketbase/services/cron_service.rb
35
+ - lib/pocketbase/services/crud_service.rb
36
+ - lib/pocketbase/services/file_service.rb
37
+ - lib/pocketbase/services/health_service.rb
38
+ - lib/pocketbase/services/log_service.rb
39
+ - lib/pocketbase/services/realtime_service.rb
40
+ - lib/pocketbase/services/record_service.rb
41
+ - lib/pocketbase/services/settings_service.rb
42
+ - lib/pocketbase/services/sql_service.rb
43
+ - lib/pocketbase/utils.rb
44
+ - lib/pocketbase/version.rb
45
+ homepage: https://github.com/azmi2409/pocketbase-ruby
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ homepage_uri: https://github.com/azmi2409/pocketbase-ruby
50
+ source_code_uri: https://github.com/azmi2409/pocketbase-ruby/tree/main
51
+ bug_tracker_uri: https://github.com/azmi2409/pocketbase-ruby/issues
52
+ changelog_uri: https://github.com/azmi2409/pocketbase-ruby/blob/main/CHANGELOG.md
53
+ documentation_uri: https://www.rubydoc.info/gems/pocketbase/0.1.1
54
+ allowed_push_host: https://rubygems.org
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '2.7'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 4.0.4
70
+ specification_version: 4
71
+ summary: Ruby SDK for PocketBase
72
+ test_files: []