@flowerforce/flowerbase 1.7.4 → 1.7.5-beta.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.
- package/dist/auth/controller.d.ts.map +1 -1
- package/dist/auth/controller.js +14 -4
- package/dist/auth/plugins/jwt.d.ts.map +1 -1
- package/dist/auth/plugins/jwt.js +8 -5
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +31 -6
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +126 -28
- package/dist/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +36 -28
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +102 -34
- package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -1
- package/dist/utils/initializer/exposeRoutes.js +2 -0
- package/package.json +1 -1
- package/src/auth/controller.ts +17 -6
- package/src/auth/plugins/jwt.test.ts +1 -1
- package/src/auth/plugins/jwt.ts +5 -8
- package/src/auth/providers/custom-function/controller.ts +35 -8
- package/src/features/functions/controller.ts +149 -31
- package/src/features/triggers/utils.ts +39 -28
- package/src/services/mongodb-atlas/index.ts +247 -142
- package/src/utils/initializer/exposeRoutes.ts +2 -0
|
@@ -277,6 +277,17 @@ const handleAuthenticationTrigger = async ({
|
|
|
277
277
|
changeStream.on('error', (error) => {
|
|
278
278
|
if (shouldIgnoreStreamError(error)) return
|
|
279
279
|
console.error('Authentication trigger change stream error', error)
|
|
280
|
+
emitTriggerEvent({
|
|
281
|
+
status: 'error',
|
|
282
|
+
triggerName,
|
|
283
|
+
triggerType,
|
|
284
|
+
functionName,
|
|
285
|
+
meta: {
|
|
286
|
+
...baseMeta,
|
|
287
|
+
event: 'CHANGE_STREAM'
|
|
288
|
+
},
|
|
289
|
+
error
|
|
290
|
+
})
|
|
280
291
|
})
|
|
281
292
|
changeStream.on('change', async function (change) {
|
|
282
293
|
const operationType = change['operationType' as keyof typeof change] as
|
|
@@ -365,13 +376,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
365
376
|
updateDescription
|
|
366
377
|
}
|
|
367
378
|
try {
|
|
368
|
-
emitTriggerEvent({
|
|
369
|
-
status: 'fired',
|
|
370
|
-
triggerName,
|
|
371
|
-
triggerType,
|
|
372
|
-
functionName,
|
|
373
|
-
meta: { ...baseMeta, event: 'LOGOUT' }
|
|
374
|
-
})
|
|
375
379
|
await GenerateContext({
|
|
376
380
|
args: [{ user: userData, ...op }],
|
|
377
381
|
app,
|
|
@@ -383,6 +387,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
383
387
|
services,
|
|
384
388
|
runAsSystem: true
|
|
385
389
|
})
|
|
390
|
+
emitTriggerEvent({
|
|
391
|
+
status: 'fired',
|
|
392
|
+
triggerName,
|
|
393
|
+
triggerType,
|
|
394
|
+
functionName,
|
|
395
|
+
meta: { ...baseMeta, event: 'LOGOUT' }
|
|
396
|
+
})
|
|
386
397
|
} catch (error) {
|
|
387
398
|
emitTriggerEvent({
|
|
388
399
|
status: 'error',
|
|
@@ -417,13 +428,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
417
428
|
updateDescription
|
|
418
429
|
}
|
|
419
430
|
try {
|
|
420
|
-
emitTriggerEvent({
|
|
421
|
-
status: 'fired',
|
|
422
|
-
triggerName,
|
|
423
|
-
triggerType,
|
|
424
|
-
functionName,
|
|
425
|
-
meta: { ...baseMeta, event: 'DELETE' }
|
|
426
|
-
})
|
|
427
431
|
await GenerateContext({
|
|
428
432
|
args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
|
|
429
433
|
app,
|
|
@@ -435,6 +439,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
435
439
|
services,
|
|
436
440
|
runAsSystem: true
|
|
437
441
|
})
|
|
442
|
+
emitTriggerEvent({
|
|
443
|
+
status: 'fired',
|
|
444
|
+
triggerName,
|
|
445
|
+
triggerType,
|
|
446
|
+
functionName,
|
|
447
|
+
meta: { ...baseMeta, event: 'DELETE' }
|
|
448
|
+
})
|
|
438
449
|
} catch (error) {
|
|
439
450
|
emitTriggerEvent({
|
|
440
451
|
status: 'error',
|
|
@@ -471,13 +482,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
471
482
|
updateDescription
|
|
472
483
|
}
|
|
473
484
|
try {
|
|
474
|
-
emitTriggerEvent({
|
|
475
|
-
status: 'fired',
|
|
476
|
-
triggerName,
|
|
477
|
-
triggerType,
|
|
478
|
-
functionName,
|
|
479
|
-
meta: { ...baseMeta, event: 'UPDATE' }
|
|
480
|
-
})
|
|
481
485
|
await GenerateContext({
|
|
482
486
|
args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
|
|
483
487
|
app,
|
|
@@ -489,6 +493,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
489
493
|
services,
|
|
490
494
|
runAsSystem: true
|
|
491
495
|
})
|
|
496
|
+
emitTriggerEvent({
|
|
497
|
+
status: 'fired',
|
|
498
|
+
triggerName,
|
|
499
|
+
triggerType,
|
|
500
|
+
functionName,
|
|
501
|
+
meta: { ...baseMeta, event: 'UPDATE' }
|
|
502
|
+
})
|
|
492
503
|
} catch (error) {
|
|
493
504
|
emitTriggerEvent({
|
|
494
505
|
status: 'error',
|
|
@@ -575,13 +586,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
575
586
|
}
|
|
576
587
|
|
|
577
588
|
try {
|
|
578
|
-
emitTriggerEvent({
|
|
579
|
-
status: 'fired',
|
|
580
|
-
triggerName,
|
|
581
|
-
triggerType,
|
|
582
|
-
functionName,
|
|
583
|
-
meta: { ...baseMeta, event: 'CREATE' }
|
|
584
|
-
})
|
|
585
589
|
await GenerateContext({
|
|
586
590
|
args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
|
|
587
591
|
app,
|
|
@@ -593,6 +597,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
593
597
|
services,
|
|
594
598
|
runAsSystem: true
|
|
595
599
|
})
|
|
600
|
+
emitTriggerEvent({
|
|
601
|
+
status: 'fired',
|
|
602
|
+
triggerName,
|
|
603
|
+
triggerType,
|
|
604
|
+
functionName,
|
|
605
|
+
meta: { ...baseMeta, event: 'CREATE' }
|
|
606
|
+
})
|
|
596
607
|
} catch (error) {
|
|
597
608
|
emitTriggerEvent({
|
|
598
609
|
status: 'error',
|
|
@@ -4,6 +4,7 @@ import isEqual from 'lodash/isEqual'
|
|
|
4
4
|
import set from 'lodash/set'
|
|
5
5
|
import unset from 'lodash/unset'
|
|
6
6
|
import {
|
|
7
|
+
ChangeStreamOptions,
|
|
7
8
|
ClientSession,
|
|
8
9
|
ClientSessionOptions,
|
|
9
10
|
Collection,
|
|
@@ -139,7 +140,73 @@ const normalizeFindOneAndUpdateOptions = (
|
|
|
139
140
|
const buildAndQuery = (clauses: MongoFilter<Document>[]): MongoFilter<Document> =>
|
|
140
141
|
clauses.length ? { $and: clauses } : {}
|
|
141
142
|
|
|
142
|
-
const
|
|
143
|
+
const toWatchMatchFilter = (value: unknown): unknown => {
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
return value.map((item) => toWatchMatchFilter(item))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!isPlainObject(value)) return value
|
|
149
|
+
|
|
150
|
+
return Object.entries(value).reduce<Record<string, unknown>>((acc, [key, current]) => {
|
|
151
|
+
if (key.startsWith('$')) {
|
|
152
|
+
acc[key] = toWatchMatchFilter(current)
|
|
153
|
+
return acc
|
|
154
|
+
}
|
|
155
|
+
acc[`fullDocument.${key}`] = toWatchMatchFilter(current)
|
|
156
|
+
return acc
|
|
157
|
+
}, {})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type RealmCompatibleWatchOptions = Document & {
|
|
161
|
+
filter?: MongoFilter<Document>
|
|
162
|
+
ids?: unknown[]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const resolveWatchArgs = (
|
|
166
|
+
pipelineOrOptions?: Document[] | RealmCompatibleWatchOptions,
|
|
167
|
+
options?: RealmCompatibleWatchOptions
|
|
168
|
+
) => {
|
|
169
|
+
const inputPipeline = Array.isArray(pipelineOrOptions) ? pipelineOrOptions : []
|
|
170
|
+
const rawOptions = (Array.isArray(pipelineOrOptions) ? options : pipelineOrOptions) ?? {}
|
|
171
|
+
|
|
172
|
+
if (!isPlainObject(rawOptions)) {
|
|
173
|
+
return {
|
|
174
|
+
pipeline: inputPipeline,
|
|
175
|
+
options: options as ChangeStreamOptions | undefined,
|
|
176
|
+
extraMatches: [] as Document[]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const {
|
|
181
|
+
filter: watchFilter,
|
|
182
|
+
ids,
|
|
183
|
+
...watchOptions
|
|
184
|
+
} = rawOptions as RealmCompatibleWatchOptions
|
|
185
|
+
|
|
186
|
+
const extraMatches: Document[] = []
|
|
187
|
+
if (typeof watchFilter !== 'undefined') {
|
|
188
|
+
extraMatches.push({ $match: toWatchMatchFilter(watchFilter) as Document })
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(ids)) {
|
|
191
|
+
extraMatches.push({
|
|
192
|
+
$match: {
|
|
193
|
+
$or: [
|
|
194
|
+
{ 'documentKey._id': { $in: ids } },
|
|
195
|
+
{ 'fullDocument._id': { $in: ids } }
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
pipeline: inputPipeline,
|
|
203
|
+
options: watchOptions as ChangeStreamOptions,
|
|
204
|
+
extraMatches
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const hasAtomicOperators = (data: Document) =>
|
|
209
|
+
Object.keys(data).some((key) => key.startsWith('$'))
|
|
143
210
|
|
|
144
211
|
const normalizeUpdatePayload = (data: Document) =>
|
|
145
212
|
hasAtomicOperators(data) ? data : { $set: data }
|
|
@@ -364,22 +431,22 @@ const getOperators: GetOperatorsFunction = (
|
|
|
364
431
|
|
|
365
432
|
return {
|
|
366
433
|
/**
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
434
|
+
* Finds a single document in a MongoDB collection with optional role-based filtering and validation.
|
|
435
|
+
*
|
|
436
|
+
* @param {Filter<Document>} query - The MongoDB query used to match the document.
|
|
437
|
+
* @param {Document} [projection] - Optional projection to select returned fields.
|
|
438
|
+
* @param {FindOneOptions} [options] - Optional settings for the findOne operation.
|
|
439
|
+
* @returns {Promise<Document | {} | null>} A promise resolving to the document if found and permitted, an empty object if access is denied, or `null` if not found.
|
|
440
|
+
*
|
|
441
|
+
* @description
|
|
442
|
+
* If `run_as_system` is enabled, the function behaves like a standard `collection.findOne(query)` with no access checks.
|
|
443
|
+
* Otherwise:
|
|
444
|
+
* - Merges the provided query with any access control filters using `getFormattedQuery`.
|
|
445
|
+
* - Attempts to find the document using the formatted query.
|
|
446
|
+
* - Determines the user's role via `getWinningRole`.
|
|
447
|
+
* - Validates the result using `checkValidation` to ensure read permission.
|
|
448
|
+
* - If validation fails, returns an empty object; otherwise returns the validated document.
|
|
449
|
+
*/
|
|
383
450
|
findOne: async (query = {}, projectionOrOptions, options) => {
|
|
384
451
|
try {
|
|
385
452
|
const { projection, options: normalizedOptions } = resolveFindArgs(
|
|
@@ -389,9 +456,9 @@ const getOperators: GetOperatorsFunction = (
|
|
|
389
456
|
const resolvedOptions =
|
|
390
457
|
projection || normalizedOptions
|
|
391
458
|
? {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
459
|
+
...(normalizedOptions ?? {}),
|
|
460
|
+
...(projection ? { projection } : {})
|
|
461
|
+
}
|
|
395
462
|
: undefined
|
|
396
463
|
const resolvedQuery = query ?? {}
|
|
397
464
|
if (!run_as_system) {
|
|
@@ -429,15 +496,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
429
496
|
})
|
|
430
497
|
const { status, document } = winningRole
|
|
431
498
|
? await checkValidation(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
499
|
+
winningRole,
|
|
500
|
+
{
|
|
501
|
+
type: 'read',
|
|
502
|
+
roles,
|
|
503
|
+
cursor: result,
|
|
504
|
+
expansions: {}
|
|
505
|
+
},
|
|
506
|
+
user
|
|
507
|
+
)
|
|
441
508
|
: fallbackAccess(result)
|
|
442
509
|
|
|
443
510
|
// Return validated document or empty object if not permitted
|
|
@@ -490,15 +557,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
490
557
|
})
|
|
491
558
|
const { status } = winningRole
|
|
492
559
|
? await checkValidation(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
560
|
+
winningRole,
|
|
561
|
+
{
|
|
562
|
+
type: 'delete',
|
|
563
|
+
roles,
|
|
564
|
+
cursor: result,
|
|
565
|
+
expansions: {}
|
|
566
|
+
},
|
|
567
|
+
user
|
|
568
|
+
)
|
|
502
569
|
: fallbackAccess(result)
|
|
503
570
|
|
|
504
571
|
if (!status) {
|
|
@@ -545,15 +612,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
545
612
|
|
|
546
613
|
const { status, document } = winningRole
|
|
547
614
|
? await checkValidation(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
615
|
+
winningRole,
|
|
616
|
+
{
|
|
617
|
+
type: 'insert',
|
|
618
|
+
roles,
|
|
619
|
+
cursor: data,
|
|
620
|
+
expansions: {}
|
|
621
|
+
},
|
|
622
|
+
user
|
|
623
|
+
)
|
|
557
624
|
: fallbackAccess(data)
|
|
558
625
|
|
|
559
626
|
if (!status || !isEqual(data, document)) {
|
|
@@ -636,15 +703,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
636
703
|
// Validate update permissions
|
|
637
704
|
const { status, document } = winningRole
|
|
638
705
|
? await checkValidation(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
706
|
+
winningRole,
|
|
707
|
+
{
|
|
708
|
+
type: 'write',
|
|
709
|
+
roles,
|
|
710
|
+
cursor: docToCheck,
|
|
711
|
+
expansions: {}
|
|
712
|
+
},
|
|
713
|
+
user
|
|
714
|
+
)
|
|
648
715
|
: fallbackAccess(docToCheck)
|
|
649
716
|
// Ensure no unauthorized changes are made
|
|
650
717
|
const areDocumentsEqual = areUpdatedFieldsAllowed(document, docToCheck, updatedPaths)
|
|
@@ -751,15 +818,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
751
818
|
const readRole = getWinningRole(updateResult, user, roles)
|
|
752
819
|
const readResult = readRole
|
|
753
820
|
? await checkValidation(
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
821
|
+
readRole,
|
|
822
|
+
{
|
|
823
|
+
type: 'read',
|
|
824
|
+
roles,
|
|
825
|
+
cursor: updateResult,
|
|
826
|
+
expansions: {}
|
|
827
|
+
},
|
|
828
|
+
user
|
|
829
|
+
)
|
|
763
830
|
: fallbackAccess(updateResult)
|
|
764
831
|
|
|
765
832
|
const sanitizedDoc = readResult.status ? (readResult.document ?? updateResult) : {}
|
|
@@ -806,9 +873,9 @@ const getOperators: GetOperatorsFunction = (
|
|
|
806
873
|
const resolvedOptions =
|
|
807
874
|
projection || normalizedOptions
|
|
808
875
|
? {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
876
|
+
...(normalizedOptions ?? {}),
|
|
877
|
+
...(projection ? { projection } : {})
|
|
878
|
+
}
|
|
812
879
|
: undefined
|
|
813
880
|
if (!run_as_system) {
|
|
814
881
|
checkDenyOperation(normalizedRules, collection.collectionName, CRUD_OPERATIONS.READ)
|
|
@@ -839,15 +906,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
839
906
|
})
|
|
840
907
|
const { status, document } = winningRole
|
|
841
908
|
? await checkValidation(
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
909
|
+
winningRole,
|
|
910
|
+
{
|
|
911
|
+
type: 'read',
|
|
912
|
+
roles,
|
|
913
|
+
cursor: currentDoc,
|
|
914
|
+
expansions: {}
|
|
915
|
+
},
|
|
916
|
+
user
|
|
917
|
+
)
|
|
851
918
|
: fallbackAccess(currentDoc)
|
|
852
919
|
|
|
853
920
|
return status ? document : undefined
|
|
@@ -928,63 +995,85 @@ const getOperators: GetOperatorsFunction = (
|
|
|
928
995
|
*
|
|
929
996
|
* This allows fine-grained control over what change events a user can observe, based on roles and filters.
|
|
930
997
|
*/
|
|
931
|
-
watch: (
|
|
998
|
+
watch: (pipelineOrOptions = [], options) => {
|
|
932
999
|
try {
|
|
1000
|
+
const {
|
|
1001
|
+
pipeline,
|
|
1002
|
+
options: watchOptions,
|
|
1003
|
+
extraMatches
|
|
1004
|
+
} = resolveWatchArgs(pipelineOrOptions as Document[] | RealmCompatibleWatchOptions, options as RealmCompatibleWatchOptions)
|
|
1005
|
+
|
|
933
1006
|
if (!run_as_system) {
|
|
934
|
-
checkDenyOperation(
|
|
1007
|
+
checkDenyOperation(
|
|
1008
|
+
normalizedRules,
|
|
1009
|
+
collection.collectionName,
|
|
1010
|
+
CRUD_OPERATIONS.READ
|
|
1011
|
+
)
|
|
935
1012
|
// Apply access filters to initial change stream pipeline
|
|
936
1013
|
const formattedQuery = getFormattedQuery(filters, {}, user)
|
|
1014
|
+
const watchFormattedQuery = formattedQuery.map(
|
|
1015
|
+
(condition) => toWatchMatchFilter(condition) as MongoFilter<Document>
|
|
1016
|
+
)
|
|
937
1017
|
|
|
938
|
-
const firstStep =
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1018
|
+
const firstStep = watchFormattedQuery.length
|
|
1019
|
+
? {
|
|
1020
|
+
$match: {
|
|
1021
|
+
$and: watchFormattedQuery
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
: undefined
|
|
943
1025
|
|
|
944
|
-
const formattedPipeline = [
|
|
945
|
-
firstStep,
|
|
946
|
-
...pipeline
|
|
947
|
-
].filter(Boolean) as Document[]
|
|
1026
|
+
const formattedPipeline = [firstStep, ...extraMatches, ...pipeline].filter(Boolean) as Document[]
|
|
948
1027
|
|
|
949
|
-
const result = collection.watch(formattedPipeline,
|
|
1028
|
+
const result = collection.watch(formattedPipeline, watchOptions)
|
|
950
1029
|
const originalOn = result.on.bind(result)
|
|
951
1030
|
|
|
952
1031
|
/**
|
|
953
1032
|
* Validates a change event against the user's roles.
|
|
954
1033
|
*
|
|
955
1034
|
* @param {Document} change - A change event from the ChangeStream.
|
|
956
|
-
* @returns {Promise<{ status: boolean, document: Document, updatedFieldsStatus: boolean, updatedFields: Document }>}
|
|
1035
|
+
* @returns {Promise<{ status: boolean, document: Document, updatedFieldsStatus: boolean, updatedFields: Document, hasFullDocument: boolean, hasWinningRole: boolean }>}
|
|
957
1036
|
*/
|
|
958
|
-
const isValidChange = async (
|
|
1037
|
+
const isValidChange = async (change: Document) => {
|
|
1038
|
+
const { fullDocument, updateDescription } = change
|
|
1039
|
+
const hasFullDocument = !!fullDocument
|
|
959
1040
|
const winningRole = getWinningRole(fullDocument, user, roles)
|
|
960
1041
|
|
|
961
|
-
const
|
|
1042
|
+
const fullDocumentValidation = winningRole
|
|
962
1043
|
? await checkValidation(
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1044
|
+
winningRole,
|
|
1045
|
+
{
|
|
1046
|
+
type: 'read',
|
|
1047
|
+
roles,
|
|
1048
|
+
cursor: fullDocument,
|
|
1049
|
+
expansions: {}
|
|
1050
|
+
},
|
|
1051
|
+
user
|
|
1052
|
+
)
|
|
972
1053
|
: fallbackAccess(fullDocument)
|
|
1054
|
+
const { status, document } = fullDocumentValidation
|
|
973
1055
|
|
|
974
1056
|
const { status: updatedFieldsStatus, document: updatedFields } = winningRole
|
|
975
1057
|
? await checkValidation(
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1058
|
+
winningRole,
|
|
1059
|
+
{
|
|
1060
|
+
type: 'read',
|
|
1061
|
+
roles,
|
|
1062
|
+
cursor: updateDescription?.updatedFields,
|
|
1063
|
+
expansions: {}
|
|
1064
|
+
},
|
|
1065
|
+
user
|
|
1066
|
+
)
|
|
985
1067
|
: fallbackAccess(updateDescription?.updatedFields)
|
|
986
1068
|
|
|
987
|
-
return {
|
|
1069
|
+
return {
|
|
1070
|
+
status,
|
|
1071
|
+
document,
|
|
1072
|
+
updatedFieldsStatus,
|
|
1073
|
+
updatedFields,
|
|
1074
|
+
hasFullDocument,
|
|
1075
|
+
hasWinningRole: !!winningRole
|
|
1076
|
+
}
|
|
988
1077
|
}
|
|
989
1078
|
|
|
990
1079
|
// Override the .on() method to apply validation before emitting events
|
|
@@ -993,9 +1082,13 @@ const getOperators: GetOperatorsFunction = (
|
|
|
993
1082
|
listener: EventsDescription[EventKey]
|
|
994
1083
|
) => {
|
|
995
1084
|
return originalOn(eventType, async (change: Document) => {
|
|
996
|
-
const {
|
|
997
|
-
|
|
998
|
-
|
|
1085
|
+
const {
|
|
1086
|
+
document,
|
|
1087
|
+
updatedFieldsStatus,
|
|
1088
|
+
updatedFields,
|
|
1089
|
+
hasFullDocument,
|
|
1090
|
+
hasWinningRole
|
|
1091
|
+
} = await isValidChange(change)
|
|
999
1092
|
|
|
1000
1093
|
const filteredChange = {
|
|
1001
1094
|
...change,
|
|
@@ -1006,6 +1099,18 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1006
1099
|
}
|
|
1007
1100
|
}
|
|
1008
1101
|
|
|
1102
|
+
console.log('[flowerbase watch] delivered change', {
|
|
1103
|
+
collection: collName,
|
|
1104
|
+
operationType: change?.operationType,
|
|
1105
|
+
eventType,
|
|
1106
|
+
hasFullDocument,
|
|
1107
|
+
hasWinningRole,
|
|
1108
|
+
updatedFieldsStatus,
|
|
1109
|
+
documentKey:
|
|
1110
|
+
change?.documentKey?._id?.toString?.() ||
|
|
1111
|
+
change?.documentKey?._id ||
|
|
1112
|
+
null
|
|
1113
|
+
})
|
|
1009
1114
|
listener(filteredChange)
|
|
1010
1115
|
})
|
|
1011
1116
|
}
|
|
@@ -1014,7 +1119,7 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1014
1119
|
}
|
|
1015
1120
|
|
|
1016
1121
|
// System mode: no filtering applied
|
|
1017
|
-
const result = collection.watch(pipeline,
|
|
1122
|
+
const result = collection.watch([...extraMatches, ...pipeline], watchOptions)
|
|
1018
1123
|
emitMongoEvent('watch')
|
|
1019
1124
|
return result
|
|
1020
1125
|
} catch (error) {
|
|
@@ -1105,15 +1210,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1105
1210
|
|
|
1106
1211
|
const { status, document } = winningRole
|
|
1107
1212
|
? await checkValidation(
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1213
|
+
winningRole,
|
|
1214
|
+
{
|
|
1215
|
+
type: 'insert',
|
|
1216
|
+
roles,
|
|
1217
|
+
cursor: currentDoc,
|
|
1218
|
+
expansions: {}
|
|
1219
|
+
},
|
|
1220
|
+
user
|
|
1221
|
+
)
|
|
1117
1222
|
: fallbackAccess(currentDoc)
|
|
1118
1223
|
|
|
1119
1224
|
return status ? document : undefined
|
|
@@ -1166,15 +1271,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1166
1271
|
|
|
1167
1272
|
const { status, document } = winningRole
|
|
1168
1273
|
? await checkValidation(
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1274
|
+
winningRole,
|
|
1275
|
+
{
|
|
1276
|
+
type: 'write',
|
|
1277
|
+
roles,
|
|
1278
|
+
cursor: currentDoc,
|
|
1279
|
+
expansions: {}
|
|
1280
|
+
},
|
|
1281
|
+
user
|
|
1282
|
+
)
|
|
1178
1283
|
: fallbackAccess(currentDoc)
|
|
1179
1284
|
|
|
1180
1285
|
return status ? document : undefined
|
|
@@ -1236,15 +1341,15 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1236
1341
|
|
|
1237
1342
|
const { status, document } = winningRole
|
|
1238
1343
|
? await checkValidation(
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1344
|
+
winningRole,
|
|
1345
|
+
{
|
|
1346
|
+
type: 'delete',
|
|
1347
|
+
roles,
|
|
1348
|
+
cursor: currentDoc,
|
|
1349
|
+
expansions: {}
|
|
1350
|
+
},
|
|
1351
|
+
user
|
|
1352
|
+
)
|
|
1248
1353
|
: fallbackAccess(currentDoc)
|
|
1249
1354
|
|
|
1250
1355
|
return status ? document : undefined
|
|
@@ -49,6 +49,7 @@ export const exposeRoutes = async (fastify: FastifyInstance) => {
|
|
|
49
49
|
const db = fastify.mongo.client.db(DB_NAME)
|
|
50
50
|
const { email, password } = req.body
|
|
51
51
|
const hashedPassword = await hashPassword(password)
|
|
52
|
+
const now = new Date()
|
|
52
53
|
|
|
53
54
|
const users = db.collection(authCollection!).find()
|
|
54
55
|
|
|
@@ -65,6 +66,7 @@ export const exposeRoutes = async (fastify: FastifyInstance) => {
|
|
|
65
66
|
email: email,
|
|
66
67
|
password: hashedPassword,
|
|
67
68
|
status: 'confirmed',
|
|
69
|
+
createdAt: now,
|
|
68
70
|
custom_data: {}
|
|
69
71
|
})
|
|
70
72
|
|