better_auth-mongo-adapter 0.7.0 → 0.8.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 +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +15 -1
- data/lib/better_auth/mongo_adapter/version.rb +1 -1
- data/lib/better_auth/mongo_adapter.rb +4 -674
- metadata +8 -62
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86afd33544ccb8e9fff5ccfa4e055cd31007086648c7985f1e9b64f40176f68e
|
|
4
|
+
data.tar.gz: 953a82389a1bbc913d702396fe0c4a66abcf79f042d4a5fb6bcd36eecaf233b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c67b4588a64b8fbb648ee5ec5fe029a67a6cd56c8c7ef35a48aacad33abecdff31f91eef0c2d110295c622cd877cbdb783a6583337c6bad0ce77985c2bdae34b
|
|
7
|
+
data.tar.gz: 86ab56e50db549d6ad11e28b06282376581651ecbdc540f15c5e2c114fab2a55ae8c21c9a7b11178826b78d9edce7a69875459c8be77a096dafec831405e6998
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Deprecated this package in favor of `better_auth-mongodb`.
|
|
6
|
+
- Kept `require "better_auth/mongo_adapter"` as a compatibility entrypoint.
|
|
7
|
+
|
|
5
8
|
## 0.7.0 - 2026-05-05
|
|
6
9
|
|
|
7
10
|
- Added explicit `ensure_indexes!` setup helper for Mongo indexes derived from Better Auth schema metadata.
|
data/README.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# better_auth-mongo-adapter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Deprecated compatibility package for Better Auth Ruby MongoDB support.
|
|
4
|
+
|
|
5
|
+
New applications should use `better_auth-mongodb`:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem "better_auth-mongodb"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
require "better_auth/mongodb"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This package remains available so existing applications that install
|
|
16
|
+
`better_auth-mongo-adapter` and require `better_auth/mongo_adapter` continue to
|
|
17
|
+
load the same adapter.
|
|
4
18
|
|
|
5
19
|
## Installation
|
|
6
20
|
|
|
@@ -1,679 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "better_auth"
|
|
4
|
-
require "mongo"
|
|
5
|
-
require "securerandom"
|
|
6
|
-
require "time"
|
|
3
|
+
warn "The better_auth-mongo-adapter gem is deprecated; use better_auth-mongodb and require \"better_auth/mongodb\" instead.", uplevel: 1
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
module Adapters
|
|
10
|
-
class MongoDB < Base
|
|
11
|
-
class MongoAdapterError < Error
|
|
12
|
-
attr_reader :code
|
|
13
|
-
|
|
14
|
-
def initialize(code, message)
|
|
15
|
-
@code = code
|
|
16
|
-
super(message)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
attr_reader :database, :client, :use_plural
|
|
21
|
-
|
|
22
|
-
def initialize(options = nil, database:, client: nil, transaction: nil, use_plural: false, session: nil)
|
|
23
|
-
require "mongo" unless database
|
|
24
|
-
|
|
25
|
-
super(options || Configuration.new(secret: Configuration::DEFAULT_SECRET, database: :memory))
|
|
26
|
-
@database = database
|
|
27
|
-
@client = client
|
|
28
|
-
@transaction_enabled = transaction.nil? ? !client.nil? : !!transaction
|
|
29
|
-
@use_plural = !!use_plural
|
|
30
|
-
@session = session
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def create(model:, data:, force_allow_id: false)
|
|
34
|
-
model = model.to_s
|
|
35
|
-
record = transform_input(model, data, "create", force_allow_id)
|
|
36
|
-
document = to_document(model, record)
|
|
37
|
-
collection_for(model).insert_one(document, session_options)
|
|
38
|
-
from_document(model, document)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def find_one(model:, where: [], select: nil, join: nil)
|
|
42
|
-
find_many(model: model, where: where, select: select, join: join, limit: 1).first
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def find_many(model:, where: [], sort_by: nil, limit: nil, offset: nil, select: nil, join: nil)
|
|
46
|
-
model = model.to_s
|
|
47
|
-
pipeline = [{"$match" => mongo_filter(model, where || [])}]
|
|
48
|
-
pipeline.concat(join_stages(model, join)) if join
|
|
49
|
-
pipeline << {"$project" => projection_for(model, select, join)} if select && !select.empty?
|
|
50
|
-
pipeline << {"$sort" => {sort_field(model, sort_by) => sort_direction(sort_by)}} if sort_by
|
|
51
|
-
pipeline << {"$skip" => offset.to_i} if offset
|
|
52
|
-
effective_limit = limit.nil? ? default_find_many_limit : limit.to_i
|
|
53
|
-
pipeline << {"$limit" => effective_limit} if effective_limit.positive?
|
|
54
|
-
|
|
55
|
-
collection_for(model)
|
|
56
|
-
.aggregate(pipeline, session_options)
|
|
57
|
-
.to_a
|
|
58
|
-
.map { |document| from_document(model, stringify_document(document), join: join) }
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def update(model:, where:, update:)
|
|
62
|
-
model = model.to_s
|
|
63
|
-
data = transform_input(model, update, "update", true)
|
|
64
|
-
document = to_document(model, data)
|
|
65
|
-
document.delete("_id")
|
|
66
|
-
result = collection_for(model).find_one_and_update(
|
|
67
|
-
mongo_filter(model, where || []),
|
|
68
|
-
{"$set" => document},
|
|
69
|
-
session_options.merge(return_document: :after)
|
|
70
|
-
)
|
|
71
|
-
result = unwrap_update_result(result)
|
|
72
|
-
result ? from_document(model, stringify_document(result)) : nil
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def update_many(model:, where:, update:)
|
|
76
|
-
model = model.to_s
|
|
77
|
-
data = transform_input(model, update, "update", true)
|
|
78
|
-
document = to_document(model, data)
|
|
79
|
-
document.delete("_id")
|
|
80
|
-
result = collection_for(model).update_many(
|
|
81
|
-
mongo_filter(model, where || []),
|
|
82
|
-
{"$set" => document},
|
|
83
|
-
session_options
|
|
84
|
-
)
|
|
85
|
-
result.respond_to?(:modified_count) ? result.modified_count : result.to_i
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def delete(model:, where:)
|
|
89
|
-
collection_for(model.to_s).delete_one(mongo_filter(model.to_s, where || []), session_options)
|
|
90
|
-
nil
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def delete_many(model:, where:)
|
|
94
|
-
result = collection_for(model.to_s).delete_many(mongo_filter(model.to_s, where || []), session_options)
|
|
95
|
-
result.respond_to?(:deleted_count) ? result.deleted_count : result.to_i
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def count(model:, where: nil)
|
|
99
|
-
pipeline = [
|
|
100
|
-
{"$match" => mongo_filter(model.to_s, where || [])},
|
|
101
|
-
{"$count" => "total"}
|
|
102
|
-
]
|
|
103
|
-
row = collection_for(model.to_s).aggregate(pipeline, session_options).to_a.first
|
|
104
|
-
return 0 unless row
|
|
105
|
-
|
|
106
|
-
(row["total"] || row[:total] || 0).to_i
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def ensure_indexes!
|
|
110
|
-
Schema.auth_tables(options).flat_map do |model, table|
|
|
111
|
-
table.fetch(:fields).filter_map do |field, attributes|
|
|
112
|
-
next if field == "id"
|
|
113
|
-
next unless attributes[:unique] || attributes[:index]
|
|
114
|
-
|
|
115
|
-
collection = collection_for(model)
|
|
116
|
-
key = storage_field(model, field)
|
|
117
|
-
index_options = attributes[:unique] ? {unique: true} : {}
|
|
118
|
-
collection.indexes.create_one({key => 1}, index_options)
|
|
119
|
-
{
|
|
120
|
-
collection: collection_name(model),
|
|
121
|
-
field: field,
|
|
122
|
-
keys: {key => 1},
|
|
123
|
-
unique: attributes[:unique] == true
|
|
124
|
-
}
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def transaction
|
|
130
|
-
return yield self unless client && @transaction_enabled && client.respond_to?(:start_session)
|
|
131
|
-
|
|
132
|
-
session = client.start_session
|
|
133
|
-
begin
|
|
134
|
-
session.start_transaction
|
|
135
|
-
adapter = self.class.new(options, database: database, client: client, transaction: @transaction_enabled, use_plural: use_plural, session: session)
|
|
136
|
-
result = yield adapter
|
|
137
|
-
session.commit_transaction
|
|
138
|
-
result
|
|
139
|
-
rescue
|
|
140
|
-
session.abort_transaction
|
|
141
|
-
raise
|
|
142
|
-
ensure
|
|
143
|
-
session.end_session
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
private
|
|
148
|
-
|
|
149
|
-
def transform_input(model, data, action, force_allow_id)
|
|
150
|
-
fields = fields_for(model)
|
|
151
|
-
input = stringify_keys(data)
|
|
152
|
-
output = {}
|
|
153
|
-
|
|
154
|
-
fields.each do |field, attributes|
|
|
155
|
-
next if field == "id" && input.key?(field) && !force_allow_id
|
|
156
|
-
|
|
157
|
-
value_provided = input.key?(field)
|
|
158
|
-
value = input[field]
|
|
159
|
-
if value_provided && attributes[:input] == false && value && !force_allow_id
|
|
160
|
-
raise APIError.new("BAD_REQUEST", message: "#{field} is not allowed to be set")
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
if !value_provided && action == "create" && attributes.key?(:default_value)
|
|
164
|
-
value = resolve_default(attributes[:default_value])
|
|
165
|
-
value_provided = true
|
|
166
|
-
elsif !value_provided && action == "update" && attributes[:on_update]
|
|
167
|
-
value = resolve_default(attributes[:on_update])
|
|
168
|
-
value_provided = true
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
if !value_provided && action == "create" && attributes[:required]
|
|
172
|
-
raise APIError.new("BAD_REQUEST", message: "#{field} is required") unless field == "id"
|
|
173
|
-
end
|
|
174
|
-
output[field] = coerce_value(value, attributes) if value_provided
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
output["id"] = generated_id if action == "create" && !output.key?("id")
|
|
178
|
-
output
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def mongo_filter(model, where)
|
|
182
|
-
clauses = Array(where)
|
|
183
|
-
return {} if clauses.empty?
|
|
184
|
-
|
|
185
|
-
conditions = clauses.map do |clause|
|
|
186
|
-
connector = if fetch_key(clause, :connector).to_s.upcase == "OR"
|
|
187
|
-
"OR"
|
|
188
|
-
else
|
|
189
|
-
"AND"
|
|
190
|
-
end
|
|
191
|
-
{condition: condition_for(model, clause), connector: connector}
|
|
192
|
-
end
|
|
193
|
-
return conditions.first.fetch(:condition) if conditions.one?
|
|
194
|
-
|
|
195
|
-
result = {}
|
|
196
|
-
and_conditions = conditions.select { |entry| entry.fetch(:connector) == "AND" }.map { |entry| entry.fetch(:condition) }
|
|
197
|
-
or_conditions = conditions.select { |entry| entry.fetch(:connector) == "OR" }.map { |entry| entry.fetch(:condition) }
|
|
198
|
-
result["$and"] = and_conditions if and_conditions.any?
|
|
199
|
-
result["$or"] = or_conditions if or_conditions.any?
|
|
200
|
-
result
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def default_find_many_limit
|
|
204
|
-
value = options.advanced.dig(:database, :default_find_many_limit)
|
|
205
|
-
return 100 if value.nil?
|
|
206
|
-
|
|
207
|
-
Integer(value)
|
|
208
|
-
rescue ArgumentError, TypeError
|
|
209
|
-
100
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def array_operator_values(value)
|
|
213
|
-
value.is_a?(Array) ? value : [value]
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def condition_for(model, clause)
|
|
217
|
-
operator = (fetch_key(clause, :operator) || "eq").to_s.downcase
|
|
218
|
-
value = fetch_key(clause, :value)
|
|
219
|
-
|
|
220
|
-
field = resolve_field(model, fetch_key(clause, :field))
|
|
221
|
-
attributes = fields_for(model).fetch(field)
|
|
222
|
-
key = (field == "id") ? "_id" : storage_field(model, field)
|
|
223
|
-
mode = (fetch_key(clause, :mode) || "sensitive").to_s
|
|
224
|
-
id_field = id_field?(field, attributes)
|
|
225
|
-
insensitive = !id_field && mode == "insensitive" && insensitive_value?(value)
|
|
226
|
-
value = coerce_where_value(value, attributes)
|
|
227
|
-
|
|
228
|
-
case operator
|
|
229
|
-
when "eq"
|
|
230
|
-
(insensitive && value.is_a?(String)) ? regex_condition(key, value, :eq, insensitive: true) : {key => store_value(field, value, attributes, strict_id: true)}
|
|
231
|
-
when "in"
|
|
232
|
-
(insensitive && value.is_a?(Array)) ? insensitive_in_condition(key, value) : {key => {"$in" => array_operator_values(value).map { |entry| store_value(field, entry, attributes, strict_id: true) }}}
|
|
233
|
-
when "not_in"
|
|
234
|
-
(insensitive && value.is_a?(Array)) ? insensitive_not_in_condition(key, value) : {key => {"$nin" => array_operator_values(value).map { |entry| store_value(field, entry, attributes, strict_id: true) }}}
|
|
235
|
-
when "ne"
|
|
236
|
-
(insensitive && value.is_a?(String)) ? {key => {"$not" => regex_for(value, :eq, insensitive: true)}} : {key => {"$ne" => store_value(field, value, attributes, strict_id: true)}}
|
|
237
|
-
when "gt", "gte", "lt", "lte"
|
|
238
|
-
{key => {"$#{operator}" => store_value(field, value, attributes, strict_id: true)}}
|
|
239
|
-
when "contains", "starts_with", "ends_with"
|
|
240
|
-
regex_condition(key, value.to_s, operator.to_sym, insensitive: insensitive)
|
|
241
|
-
else
|
|
242
|
-
raise MongoAdapterError.new("UNSUPPORTED_OPERATOR", "Unsupported operator: #{operator}")
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def insensitive_value?(value)
|
|
247
|
-
value.is_a?(String) || (value.is_a?(Array) && value.all? { |entry| entry.is_a?(String) })
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def insensitive_in_condition(key, values)
|
|
251
|
-
return {"$expr" => {"$eq" => [1, 0]}} if values.empty?
|
|
252
|
-
|
|
253
|
-
{"$or" => values.map { |value| regex_condition(key, value, :eq, insensitive: true) }}
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def insensitive_not_in_condition(key, values)
|
|
257
|
-
return {} if values.empty?
|
|
258
|
-
|
|
259
|
-
{"$nor" => values.map { |value| regex_condition(key, value, :eq, insensitive: true) }}
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def regex_condition(key, value, operator, insensitive:)
|
|
263
|
-
{key => regex_for(value, operator, insensitive: insensitive)}
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def regex_for(value, operator, insensitive:)
|
|
267
|
-
escaped = Regexp.escape(value.to_s[0, 256])
|
|
268
|
-
pattern = case operator.to_s
|
|
269
|
-
when "eq" then "\\A#{escaped}\\z"
|
|
270
|
-
when "starts_with" then "\\A#{escaped}"
|
|
271
|
-
when "ends_with" then "#{escaped}\\z"
|
|
272
|
-
else escaped
|
|
273
|
-
end
|
|
274
|
-
Regexp.new(pattern, insensitive ? Regexp::IGNORECASE : nil)
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
def join_stages(model, join)
|
|
278
|
-
normalized_join(model, join).flat_map do |join_model, config|
|
|
279
|
-
local_field = storage_field_for_join(model, config.fetch(:from))
|
|
280
|
-
foreign_field = storage_field_for_join(join_model, config.fetch(:to))
|
|
281
|
-
relation = config[:relation]
|
|
282
|
-
limit = config.key?(:limit) ? config[:limit] : nil
|
|
283
|
-
effective_limit = limit.nil? ? default_find_many_limit : limit.to_i
|
|
284
|
-
unique = relation == "one-to-one" || config[:unique]
|
|
285
|
-
should_limit = !unique && effective_limit.positive?
|
|
286
|
-
|
|
287
|
-
lookup = if should_limit
|
|
288
|
-
{
|
|
289
|
-
"$lookup" => {
|
|
290
|
-
"from" => collection_name(join_model),
|
|
291
|
-
"let" => {"localFieldValue" => "$#{local_field}"},
|
|
292
|
-
"pipeline" => [
|
|
293
|
-
{"$match" => {"$expr" => {"$eq" => ["$#{foreign_field}", "$$localFieldValue"]}}},
|
|
294
|
-
{"$limit" => effective_limit}
|
|
295
|
-
],
|
|
296
|
-
"as" => join_model
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
else
|
|
300
|
-
{
|
|
301
|
-
"$lookup" => {
|
|
302
|
-
"from" => collection_name(join_model),
|
|
303
|
-
"localField" => local_field,
|
|
304
|
-
"foreignField" => foreign_field,
|
|
305
|
-
"as" => join_model
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
unique ? [lookup, {"$unwind" => {"path" => "$#{join_model}", "preserveNullAndEmptyArrays" => true}}] : [lookup]
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
def normalized_join(model, join)
|
|
315
|
-
join.each_with_object({}) do |(join_model, config), result|
|
|
316
|
-
join_model = join_model.to_s
|
|
317
|
-
result[join_model] = normalize_join_config(model, join_model, config)
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
def normalize_join_config(model, join_model, config)
|
|
322
|
-
if config.is_a?(Hash) && (config.key?(:on) || config.key?("on"))
|
|
323
|
-
on = config[:on] || config["on"]
|
|
324
|
-
relation = config[:relation] || config["relation"]
|
|
325
|
-
limit = config[:limit] || config["limit"]
|
|
326
|
-
from = fetch_key(on, :from)
|
|
327
|
-
to = fetch_key(on, :to)
|
|
328
|
-
return {from: Schema.storage_key(from), to: Schema.storage_key(to), relation: relation, limit: limit, unique: unique_join_field?(join_model, to)}
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
inferred = inferred_join_config(model, join_model)
|
|
332
|
-
if config.is_a?(Hash)
|
|
333
|
-
limit = config[:limit] || config["limit"]
|
|
334
|
-
relation = config[:relation] || config["relation"]
|
|
335
|
-
inferred = inferred.merge(limit: limit) if limit
|
|
336
|
-
inferred = inferred.merge(relation: relation) if relation
|
|
337
|
-
end
|
|
338
|
-
inferred
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
def inferred_join_config(model, join_model)
|
|
342
|
-
base_model = default_model_name(model)
|
|
343
|
-
target_model = default_model_name(join_model)
|
|
344
|
-
foreign_keys = fields_for(target_model).select do |_field, attributes|
|
|
345
|
-
reference_model_matches?(attributes, base_model)
|
|
346
|
-
end
|
|
347
|
-
forward_join = true
|
|
348
|
-
|
|
349
|
-
if foreign_keys.empty?
|
|
350
|
-
foreign_keys = fields_for(base_model).select do |_field, attributes|
|
|
351
|
-
reference_model_matches?(attributes, target_model)
|
|
352
|
-
end
|
|
353
|
-
forward_join = false
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
if foreign_keys.empty?
|
|
357
|
-
raise Error, "No foreign key found for model #{join_model} and base model #{model} while performing join operation."
|
|
358
|
-
end
|
|
359
|
-
if foreign_keys.length > 1
|
|
360
|
-
raise Error, "Multiple foreign keys found for model #{join_model} and base model #{model} while performing join operation. Only one foreign key is supported."
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
foreign_key, attributes = foreign_keys.first
|
|
364
|
-
reference = attributes.fetch(:references)
|
|
365
|
-
if forward_join
|
|
366
|
-
unique = attributes[:unique] == true
|
|
367
|
-
{from: reference.fetch(:field).to_s, to: foreign_key, relation: unique ? "one-to-one" : "one-to-many", unique: unique}
|
|
368
|
-
else
|
|
369
|
-
{from: foreign_key, to: reference.fetch(:field).to_s, relation: "one-to-one", unique: true}
|
|
370
|
-
end
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
def reference_model_matches?(attributes, model)
|
|
374
|
-
reference = attributes[:references]
|
|
375
|
-
return false unless reference
|
|
376
|
-
|
|
377
|
-
default_model_name(reference[:model] || reference["model"]) == model
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
def unique_join_field?(model, field)
|
|
381
|
-
field = resolve_field(model, field)
|
|
382
|
-
field == "id" || fields_for(model).dig(field, :unique) == true
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
def storage_field_for_join(model, field)
|
|
386
|
-
field = resolve_field(model, field)
|
|
387
|
-
(field == "id") ? "_id" : storage_field(model, field)
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
def projection_for(model, select, join)
|
|
391
|
-
selected_fields = Array(select).map { |field| storage_field_for_join(model, field) }
|
|
392
|
-
Array(select).each_with_object({}) do |field, projection|
|
|
393
|
-
projection[storage_field_for_join(model, field)] = 1
|
|
394
|
-
end.tap do |projection|
|
|
395
|
-
projection["_id"] = 0 unless selected_fields.include?("_id")
|
|
396
|
-
normalized_join(model, join).each_key { |join_model| projection[join_model] = 1 } if join
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
def sort_field(model, sort_by)
|
|
401
|
-
field = resolve_field(model, fetch_key(sort_by, :field))
|
|
402
|
-
storage_field_for_join(model, field)
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
def sort_direction(sort_by)
|
|
406
|
-
(fetch_key(sort_by, :direction).to_s == "desc") ? -1 : 1
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
def collection_for(model)
|
|
410
|
-
database.collection(collection_name(model))
|
|
411
|
-
end
|
|
5
|
+
require "better_auth/mongodb"
|
|
412
6
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
configured = configured_model_name(model)
|
|
416
|
-
return "#{configured}s" if configured && use_plural
|
|
417
|
-
return configured if configured
|
|
418
|
-
return schema_for(model).fetch(:model_name) if use_plural
|
|
419
|
-
|
|
420
|
-
model.to_s
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
def to_document(model, record)
|
|
424
|
-
fields_for(model).each_with_object({}) do |(field, attributes), document|
|
|
425
|
-
next unless record.key?(field)
|
|
426
|
-
|
|
427
|
-
key = (field == "id") ? "_id" : storage_field(model, field)
|
|
428
|
-
document[key] = store_value(field, record[field], attributes)
|
|
429
|
-
end
|
|
430
|
-
end
|
|
431
|
-
|
|
432
|
-
def from_document(model, document, join: nil)
|
|
433
|
-
fields = fields_for(model)
|
|
434
|
-
record = fields.each_with_object({}) do |(field, attributes), output|
|
|
435
|
-
key = (field == "id") ? "_id" : storage_field(model, field)
|
|
436
|
-
output[field] = output_value(field, fetch_document(document, key), attributes) if document_key?(document, key)
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
if join
|
|
440
|
-
normalized_join(model, join).each do |join_model, config|
|
|
441
|
-
next unless document_key?(document, join_model)
|
|
442
|
-
|
|
443
|
-
joined_value = fetch_document(document, join_model)
|
|
444
|
-
record[join_model] = if joined_value.is_a?(Array)
|
|
445
|
-
joined_value.map { |entry| from_document(join_model, stringify_document(entry)) }
|
|
446
|
-
elsif joined_value
|
|
447
|
-
from_document(join_model, stringify_document(joined_value))
|
|
448
|
-
elsif config[:relation] == "one-to-one"
|
|
449
|
-
nil
|
|
450
|
-
else
|
|
451
|
-
[]
|
|
452
|
-
end
|
|
453
|
-
end
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
record
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
def stringify_document(document)
|
|
460
|
-
document.each_with_object({}) { |(key, value), result| result[key.to_s] = value }
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
def unwrap_update_result(result)
|
|
464
|
-
return result unless result.is_a?(Hash)
|
|
465
|
-
return result if document_key?(result, "_id")
|
|
466
|
-
|
|
467
|
-
if result.key?("value") && (result.key?("ok") || result.key?("lastErrorObject"))
|
|
468
|
-
return result["value"]
|
|
469
|
-
end
|
|
470
|
-
if result.key?(:value) && (result.key?(:ok) || result.key?(:last_error_object))
|
|
471
|
-
return result[:value]
|
|
472
|
-
end
|
|
473
|
-
|
|
474
|
-
result
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
def store_value(field, value, attributes, strict_id: false)
|
|
478
|
-
return nil if value.nil?
|
|
479
|
-
return Array(value).map { |entry| store_value(field, entry, attributes, strict_id: strict_id) } if value.is_a?(Array)
|
|
480
|
-
|
|
481
|
-
if id_field?(field, attributes)
|
|
482
|
-
return value if custom_id_generator?
|
|
483
|
-
return bson_id(value, strict: strict_id)
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
input_value(value, attributes)
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
def output_value(field, value, attributes)
|
|
490
|
-
return nil if value.nil?
|
|
491
|
-
if id_field?(field, attributes)
|
|
492
|
-
return value.to_uuid if bson_uuid?(value)
|
|
493
|
-
return value.to_s if value.is_a?(BSON::ObjectId)
|
|
494
|
-
return value.map { |entry| output_value(field, entry, attributes) } if value.is_a?(Array)
|
|
495
|
-
return value
|
|
496
|
-
end
|
|
497
|
-
|
|
498
|
-
output_scalar_value(value, attributes)
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
def id_field?(field, attributes)
|
|
502
|
-
field.to_s == "id" || attributes.dig(:references, :field) == "id"
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
def bson_id(value, strict:)
|
|
506
|
-
if use_uuid_ids?
|
|
507
|
-
return value if bson_uuid?(value)
|
|
508
|
-
return BSON::Binary.from_uuid(value.to_s) if value.is_a?(String)
|
|
509
|
-
raise MongoAdapterError.new("INVALID_ID", "Invalid id value") if strict
|
|
510
|
-
|
|
511
|
-
return value
|
|
512
|
-
end
|
|
513
|
-
|
|
514
|
-
return value if value.is_a?(BSON::ObjectId)
|
|
515
|
-
return BSON::ObjectId.from_string(value.to_s) if value.is_a?(String)
|
|
516
|
-
raise MongoAdapterError.new("INVALID_ID", "Invalid id value") if strict
|
|
517
|
-
|
|
518
|
-
value
|
|
519
|
-
rescue BSON::Error::InvalidObjectId, ArgumentError
|
|
520
|
-
value
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
def bson_uuid?(value)
|
|
524
|
-
defined?(BSON::Binary) && value.is_a?(BSON::Binary) && value.respond_to?(:to_uuid) && value.type == :uuid
|
|
525
|
-
end
|
|
526
|
-
|
|
527
|
-
def generated_id
|
|
528
|
-
generator = options.advanced.dig(:database, :generate_id)
|
|
529
|
-
return generator.call if generator.respond_to?(:call)
|
|
530
|
-
return SecureRandom.uuid if use_uuid_ids?
|
|
531
|
-
return BSON::ObjectId.new.to_s if defined?(BSON::ObjectId)
|
|
532
|
-
|
|
533
|
-
SecureRandom.hex(12)
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
def use_uuid_ids?
|
|
537
|
-
options.advanced.dig(:database, :generate_id) == "uuid"
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
def custom_id_generator?
|
|
541
|
-
options.advanced.dig(:database, :generate_id).respond_to?(:call)
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
def resolve_default(default)
|
|
545
|
-
default.respond_to?(:call) ? default.call : default
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
def coerce_value(value, attributes)
|
|
549
|
-
return value if value.nil?
|
|
550
|
-
return Time.parse(value) if attributes[:type] == "date" && value.is_a?(String)
|
|
551
|
-
|
|
552
|
-
value
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
def input_value(value, attributes)
|
|
556
|
-
value = coerce_value(value, attributes)
|
|
557
|
-
return JSON.generate(value) if attributes[:type] == "json" && (value.is_a?(Hash) || value.is_a?(Array))
|
|
558
|
-
|
|
559
|
-
value
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
def output_scalar_value(value, attributes)
|
|
563
|
-
return JSON.parse(value) if attributes[:type] == "json" && value.is_a?(String)
|
|
564
|
-
|
|
565
|
-
coerce_value(value, attributes)
|
|
566
|
-
rescue JSON::ParserError
|
|
567
|
-
value
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
def coerce_where_value(value, attributes)
|
|
571
|
-
return value.map { |entry| coerce_where_value(entry, attributes) } if value.is_a?(Array)
|
|
572
|
-
return value == "true" if attributes[:type] == "boolean" && value.is_a?(String)
|
|
573
|
-
if attributes[:type] == "number" && value.is_a?(String) && !value.strip.empty?
|
|
574
|
-
parsed = Float(value)
|
|
575
|
-
return parsed.to_i if parsed.to_i == parsed
|
|
576
|
-
|
|
577
|
-
return parsed
|
|
578
|
-
end
|
|
579
|
-
return JSON.generate(value) if attributes[:type] == "json" && (value.is_a?(Hash) || value.is_a?(Array))
|
|
580
|
-
|
|
581
|
-
value
|
|
582
|
-
rescue ArgumentError
|
|
583
|
-
value
|
|
584
|
-
end
|
|
585
|
-
|
|
586
|
-
def session_options
|
|
587
|
-
@session ? {session: @session} : {}
|
|
588
|
-
end
|
|
589
|
-
|
|
590
|
-
def document_key?(document, key)
|
|
591
|
-
document.key?(key) || document.key?(key.to_sym)
|
|
592
|
-
end
|
|
593
|
-
|
|
594
|
-
def fetch_document(document, key)
|
|
595
|
-
return document[key] if document.key?(key)
|
|
596
|
-
|
|
597
|
-
document[key.to_sym]
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
def stringify_keys(data)
|
|
601
|
-
data.each_with_object({}) do |(key, value), result|
|
|
602
|
-
result[Schema.storage_key(key)] = value
|
|
603
|
-
end
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
def fetch_key(hash, key)
|
|
607
|
-
[key, key.to_s, Schema.storage_key(key), Schema.storage_key(key).to_sym].each do |candidate|
|
|
608
|
-
return hash[candidate] if hash.key?(candidate)
|
|
609
|
-
end
|
|
610
|
-
nil
|
|
611
|
-
end
|
|
612
|
-
|
|
613
|
-
def schema_for(model)
|
|
614
|
-
Schema.auth_tables(options).fetch(default_model_name(model))
|
|
615
|
-
end
|
|
616
|
-
|
|
617
|
-
def fields_for(model)
|
|
618
|
-
schema_for(model).fetch(:fields).merge("id" => {type: "string", required: true})
|
|
619
|
-
end
|
|
620
|
-
|
|
621
|
-
def default_model_name(model)
|
|
622
|
-
model = model.to_s
|
|
623
|
-
tables = Schema.auth_tables(options)
|
|
624
|
-
return model if tables.key?(model)
|
|
625
|
-
|
|
626
|
-
pluraless = model.end_with?("s") ? model[0...-1] : nil
|
|
627
|
-
return pluraless if pluraless && tables.key?(pluraless)
|
|
628
|
-
|
|
629
|
-
matched = tables.find { |_key, table| table[:model_name].to_s == model }
|
|
630
|
-
return matched.first if matched
|
|
631
|
-
|
|
632
|
-
raise Error, "Model \"#{model}\" not found in schema"
|
|
633
|
-
end
|
|
634
|
-
|
|
635
|
-
def configured_model_name(model)
|
|
636
|
-
configured = configured_model_option(model, :model_name)
|
|
637
|
-
return configured.to_s if configured
|
|
638
|
-
|
|
639
|
-
return nil if core_model?(model)
|
|
640
|
-
|
|
641
|
-
table_model_name = schema_for(model).fetch(:model_name).to_s
|
|
642
|
-
(table_model_name == physical_name(model)) ? nil : table_model_name
|
|
643
|
-
end
|
|
644
|
-
|
|
645
|
-
def configured_model_option(model, key)
|
|
646
|
-
data = options.respond_to?(model.to_sym) ? options.public_send(model.to_sym) : nil
|
|
647
|
-
data[key] || data[key.to_s] if data.respond_to?(:[])
|
|
648
|
-
end
|
|
649
|
-
|
|
650
|
-
def core_model?(model)
|
|
651
|
-
["user", "session", "account", "verification", "rateLimit"].include?(model.to_s)
|
|
652
|
-
end
|
|
653
|
-
|
|
654
|
-
def resolve_field(model, field)
|
|
655
|
-
field = Schema.storage_key(field)
|
|
656
|
-
return "id" if field == "id" || field == "_id"
|
|
657
|
-
|
|
658
|
-
fields = fields_for(model)
|
|
659
|
-
return field if fields.key?(field)
|
|
660
|
-
|
|
661
|
-
matched = fields.find { |_key, attributes| attributes[:field_name].to_s == field.to_s }
|
|
662
|
-
return matched.first if matched
|
|
663
|
-
|
|
664
|
-
raise Error, "Field #{field} not found in model #{model}"
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
def storage_field(model, field)
|
|
668
|
-
fields_for(model).fetch(field.to_s).fetch(:field_name, physical_name(field))
|
|
669
|
-
end
|
|
670
|
-
|
|
671
|
-
def physical_name(value)
|
|
672
|
-
value.to_s
|
|
673
|
-
.gsub(/([a-z\d])([A-Z])/, "\\1_\\2")
|
|
674
|
-
.tr("-", "_")
|
|
675
|
-
.downcase
|
|
676
|
-
end
|
|
677
|
-
end
|
|
678
|
-
end
|
|
7
|
+
module BetterAuth
|
|
8
|
+
MongoAdapter = MongoDB unless const_defined?(:MongoAdapter, false)
|
|
679
9
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_auth-mongo-adapter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
@@ -10,73 +10,19 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: better_auth
|
|
13
|
+
name: better_auth-mongodb
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- -
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.1'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.1'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: bigdecimal
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - ">="
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '3.1'
|
|
33
|
-
- - "<"
|
|
16
|
+
- - '='
|
|
34
17
|
- !ruby/object:Gem::Version
|
|
35
|
-
version:
|
|
18
|
+
version: 0.8.0
|
|
36
19
|
type: :runtime
|
|
37
20
|
prerelease: false
|
|
38
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
39
22
|
requirements:
|
|
40
|
-
- -
|
|
41
|
-
- !ruby/object:Gem::Version
|
|
42
|
-
version: '3.1'
|
|
43
|
-
- - "<"
|
|
44
|
-
- !ruby/object:Gem::Version
|
|
45
|
-
version: '5.0'
|
|
46
|
-
- !ruby/object:Gem::Dependency
|
|
47
|
-
name: logger
|
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
|
49
|
-
requirements:
|
|
50
|
-
- - ">="
|
|
51
|
-
- !ruby/object:Gem::Version
|
|
52
|
-
version: '1.6'
|
|
53
|
-
- - "<"
|
|
54
|
-
- !ruby/object:Gem::Version
|
|
55
|
-
version: '2.0'
|
|
56
|
-
type: :runtime
|
|
57
|
-
prerelease: false
|
|
58
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
59
|
-
requirements:
|
|
60
|
-
- - ">="
|
|
61
|
-
- !ruby/object:Gem::Version
|
|
62
|
-
version: '1.6'
|
|
63
|
-
- - "<"
|
|
64
|
-
- !ruby/object:Gem::Version
|
|
65
|
-
version: '2.0'
|
|
66
|
-
- !ruby/object:Gem::Dependency
|
|
67
|
-
name: mongo
|
|
68
|
-
requirement: !ruby/object:Gem::Requirement
|
|
69
|
-
requirements:
|
|
70
|
-
- - "~>"
|
|
71
|
-
- !ruby/object:Gem::Version
|
|
72
|
-
version: '2.21'
|
|
73
|
-
type: :runtime
|
|
74
|
-
prerelease: false
|
|
75
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
76
|
-
requirements:
|
|
77
|
-
- - "~>"
|
|
23
|
+
- - '='
|
|
78
24
|
- !ruby/object:Gem::Version
|
|
79
|
-
version:
|
|
25
|
+
version: 0.8.0
|
|
80
26
|
- !ruby/object:Gem::Dependency
|
|
81
27
|
name: bundler
|
|
82
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -147,8 +93,8 @@ dependencies:
|
|
|
147
93
|
- - "~>"
|
|
148
94
|
- !ruby/object:Gem::Version
|
|
149
95
|
version: '0.22'
|
|
150
|
-
description:
|
|
151
|
-
|
|
96
|
+
description: Deprecated compatibility package for Better Auth Ruby MongoDB support.
|
|
97
|
+
Use the better_auth-mongodb gem and require "better_auth/mongodb" instead.
|
|
152
98
|
email:
|
|
153
99
|
- sebastian.sala.tech@gmail.com
|
|
154
100
|
executables: []
|