@flowerforce/flowerbase 1.8.4-beta.4 → 1.8.4-beta.6
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/README.md +3 -0
- package/dist/auth/plugins/jwt.d.ts +1 -1
- package/dist/auth/plugins/jwt.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +2 -1
- package/dist/cli/call-function.js +2 -1
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +31 -3
- package/dist/features/functions/dtos.d.ts +2 -0
- package/dist/features/functions/dtos.d.ts.map +1 -1
- package/dist/features/functions/interface.d.ts +2 -0
- package/dist/features/functions/interface.d.ts.map +1 -1
- package/dist/features/functions/utils.d.ts +3 -1
- package/dist/features/functions/utils.d.ts.map +1 -1
- package/dist/features/functions/utils.js +3 -1
- package/dist/services/index.d.ts +8 -8
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +91 -20
- package/dist/services/mongodb-atlas/model.d.ts +2 -0
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/utils/context/helpers.d.ts +35 -32
- package/dist/utils/context/helpers.d.ts.map +1 -1
- package/dist/utils/context/helpers.js +19 -2
- package/dist/utils/context/interface.d.ts +1 -1
- package/dist/utils/context/interface.d.ts.map +1 -1
- package/dist/utils/roles/machines/read/D/validators.d.ts +1 -1
- package/dist/utils/roles/machines/read/D/validators.d.ts.map +1 -1
- package/dist/utils/roles/machines/write/C/validators.d.ts +1 -1
- package/dist/utils/roles/machines/write/C/validators.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/auth/providers/custom-function/controller.ts +2 -1
- package/src/cli/call-function.ts +2 -1
- package/src/features/functions/__tests__/controller.test.ts +42 -2
- package/src/features/functions/__tests__/utils.test.ts +44 -0
- package/src/features/functions/controller.ts +38 -2
- package/src/features/functions/dtos.ts +2 -0
- package/src/features/functions/interface.ts +2 -0
- package/src/features/functions/utils.ts +13 -0
- package/src/services/mongodb-atlas/__tests__/realmCompatibility.test.ts +116 -0
- package/src/services/mongodb-atlas/index.ts +122 -25
- package/src/services/mongodb-atlas/model.ts +8 -0
- package/src/utils/context/helpers.ts +18 -2
- package/src/utils/context/interface.ts +1 -1
- package/tsconfig.json +0 -5
- package/tsconfig.spec.json +2 -1
|
@@ -492,6 +492,26 @@ const areUpdatedFieldsAllowed = (
|
|
|
492
492
|
return updatedPaths.every((path) => isEqual(get(filtered, path), get(updated, path)))
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
const appendDistinctValue = (values: unknown[], candidate: unknown) => {
|
|
496
|
+
if (typeof candidate === 'undefined') return
|
|
497
|
+
if (!values.some((entry) => isEqual(entry, candidate))) {
|
|
498
|
+
values.push(candidate)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const collectDistinctValues = (documents: Document[], key: string) =>
|
|
503
|
+
documents.reduce<unknown[]>((values, document) => {
|
|
504
|
+
const currentValue = get(document, key)
|
|
505
|
+
|
|
506
|
+
if (Array.isArray(currentValue)) {
|
|
507
|
+
currentValue.forEach((entry) => appendDistinctValue(values, entry))
|
|
508
|
+
return values
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
appendDistinctValue(values, currentValue)
|
|
512
|
+
return values
|
|
513
|
+
}, [])
|
|
514
|
+
|
|
495
515
|
const getOperators: GetOperatorsFunction = (
|
|
496
516
|
mongo,
|
|
497
517
|
{ rules, dbName, collName, user, run_as_system, monitoringOrigin }
|
|
@@ -535,7 +555,48 @@ const getOperators: GetOperatorsFunction = (
|
|
|
535
555
|
})
|
|
536
556
|
}
|
|
537
557
|
|
|
538
|
-
|
|
558
|
+
const validateReadableDocument = async (currentDoc: Document) => {
|
|
559
|
+
const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
|
|
560
|
+
|
|
561
|
+
logDebug('find winningRole', {
|
|
562
|
+
collection: collName,
|
|
563
|
+
userId: getUserId(user),
|
|
564
|
+
winningRoleName: winningRole?.name ?? null,
|
|
565
|
+
rolesLength: roles.length
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
const { status, document } = winningRole
|
|
569
|
+
? await checkValidation(
|
|
570
|
+
winningRole,
|
|
571
|
+
{
|
|
572
|
+
type: 'read',
|
|
573
|
+
roles,
|
|
574
|
+
cursor: currentDoc,
|
|
575
|
+
expansions: getValidationExpansions(currentDoc)
|
|
576
|
+
},
|
|
577
|
+
user
|
|
578
|
+
)
|
|
579
|
+
: fallbackAccess(currentDoc)
|
|
580
|
+
|
|
581
|
+
return status ? document : undefined
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const getScopedQuery = (query: MongoFilter<Document> = {}) => {
|
|
585
|
+
const formattedQuery = getFormattedQuery(filters, query, user)
|
|
586
|
+
const currentQuery = formattedQuery.length ? { $and: formattedQuery } : {}
|
|
587
|
+
const safeQuery = Array.isArray(formattedQuery)
|
|
588
|
+
? normalizeQuery(formattedQuery)
|
|
589
|
+
: formattedQuery
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
formattedQuery,
|
|
593
|
+
currentQuery,
|
|
594
|
+
safeQuery,
|
|
595
|
+
builtQuery: buildAndQuery(safeQuery)
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const operators: ReturnType<GetOperatorsFunction> = {
|
|
539
600
|
/**
|
|
540
601
|
* Finds a single document in a MongoDB collection with optional role-based filtering and validation.
|
|
541
602
|
*
|
|
@@ -1068,30 +1129,7 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1068
1129
|
const response = await originalToArray()
|
|
1069
1130
|
|
|
1070
1131
|
const filteredResponse = await Promise.all(
|
|
1071
|
-
response.map(
|
|
1072
|
-
const winningRole = await getWinningRoleAsync(currentDoc, user, roles)
|
|
1073
|
-
|
|
1074
|
-
logDebug('find winningRole', {
|
|
1075
|
-
collection: collName,
|
|
1076
|
-
userId: getUserId(user),
|
|
1077
|
-
winningRoleName: winningRole?.name ?? null,
|
|
1078
|
-
rolesLength: roles.length
|
|
1079
|
-
})
|
|
1080
|
-
const { status, document } = winningRole
|
|
1081
|
-
? await checkValidation(
|
|
1082
|
-
winningRole,
|
|
1083
|
-
{
|
|
1084
|
-
type: 'read',
|
|
1085
|
-
roles,
|
|
1086
|
-
cursor: currentDoc,
|
|
1087
|
-
expansions: getValidationExpansions(currentDoc)
|
|
1088
|
-
},
|
|
1089
|
-
user
|
|
1090
|
-
)
|
|
1091
|
-
: fallbackAccess(currentDoc)
|
|
1092
|
-
|
|
1093
|
-
return status ? document : undefined
|
|
1094
|
-
})
|
|
1132
|
+
response.map((currentDoc) => validateReadableDocument(currentDoc))
|
|
1095
1133
|
)
|
|
1096
1134
|
|
|
1097
1135
|
return filteredResponse.filter(Boolean) as WithId<Document>[]
|
|
@@ -1164,6 +1202,45 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1164
1202
|
throw error
|
|
1165
1203
|
}
|
|
1166
1204
|
},
|
|
1205
|
+
distinct: async (key, query = {}, options) => {
|
|
1206
|
+
try {
|
|
1207
|
+
if (!key) {
|
|
1208
|
+
throw new Error('distinct key is required')
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (!run_as_system) {
|
|
1212
|
+
checkDenyOperation(
|
|
1213
|
+
normalizedRules,
|
|
1214
|
+
collection.collectionName,
|
|
1215
|
+
CRUD_OPERATIONS.READ
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
const { currentQuery } = getScopedQuery(query)
|
|
1219
|
+
const projectedOptions = {
|
|
1220
|
+
...(options ?? {}),
|
|
1221
|
+
projection: { _id: 1, [key]: 1 }
|
|
1222
|
+
} as FindOptions
|
|
1223
|
+
const documents = await collection.find(currentQuery, projectedOptions).toArray()
|
|
1224
|
+
const readableDocuments = (
|
|
1225
|
+
await Promise.all(documents.map((currentDoc) => validateReadableDocument(currentDoc)))
|
|
1226
|
+
).filter(Boolean) as Document[]
|
|
1227
|
+
const result = collectDistinctValues(readableDocuments, key)
|
|
1228
|
+
|
|
1229
|
+
emitMongoEvent('distinct')
|
|
1230
|
+
return result
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const result =
|
|
1234
|
+
typeof options === 'undefined'
|
|
1235
|
+
? await collection.distinct(key, query)
|
|
1236
|
+
: await collection.distinct(key, query, options)
|
|
1237
|
+
emitMongoEvent('distinct')
|
|
1238
|
+
return result
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
emitMongoEvent('distinct', undefined, error)
|
|
1241
|
+
throw error
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1167
1244
|
/**
|
|
1168
1245
|
* Watches changes on a MongoDB collection with optional role-based filtering of change events.
|
|
1169
1246
|
*
|
|
@@ -1450,6 +1527,24 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1450
1527
|
throw error
|
|
1451
1528
|
}
|
|
1452
1529
|
},
|
|
1530
|
+
bulkWrite: async (operations, options) => {
|
|
1531
|
+
try {
|
|
1532
|
+
if (!run_as_system) {
|
|
1533
|
+
throw new Error('bulkWrite is available only when run_as_system is enabled')
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const result = await collection.bulkWrite(operations, options)
|
|
1537
|
+
emitMongoEvent('bulkWrite', { operations: operations.length })
|
|
1538
|
+
return result
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
emitMongoEvent(
|
|
1541
|
+
'bulkWrite',
|
|
1542
|
+
{ operations: Array.isArray(operations) ? operations.length : 0 },
|
|
1543
|
+
error
|
|
1544
|
+
)
|
|
1545
|
+
throw error
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1453
1548
|
updateMany: async (query, data, options) => {
|
|
1454
1549
|
try {
|
|
1455
1550
|
const normalizedData = normalizeUpdatePayload(data as Document)
|
|
@@ -1602,6 +1697,8 @@ const getOperators: GetOperatorsFunction = (
|
|
|
1602
1697
|
}
|
|
1603
1698
|
}
|
|
1604
1699
|
}
|
|
1700
|
+
|
|
1701
|
+
return operators
|
|
1605
1702
|
}
|
|
1606
1703
|
|
|
1607
1704
|
const MongodbAtlas: MongodbAtlasFunction = (
|
|
@@ -93,6 +93,14 @@ export type GetOperatorsFunction = (
|
|
|
93
93
|
aggregate: (
|
|
94
94
|
...params: [...Parameters<Method<'aggregate'>>, isClient: boolean]
|
|
95
95
|
) => ReturnType<Method<'aggregate'>>
|
|
96
|
+
distinct: (
|
|
97
|
+
key: Parameters<Method<'distinct'>>[0],
|
|
98
|
+
filter?: Parameters<Method<'distinct'>>[1],
|
|
99
|
+
options?: Parameters<Method<'distinct'>>[2]
|
|
100
|
+
) => ReturnType<Method<'distinct'>>
|
|
101
|
+
bulkWrite: (
|
|
102
|
+
...params: Parameters<Method<'bulkWrite'>>
|
|
103
|
+
) => ReturnType<Method<'bulkWrite'>>
|
|
96
104
|
insertMany: (
|
|
97
105
|
...params: Parameters<Method<'insertMany'>>
|
|
98
106
|
) => ReturnType<Method<'insertMany'>>
|
|
@@ -187,8 +187,24 @@ export const generateContextData = ({
|
|
|
187
187
|
},
|
|
188
188
|
context: {
|
|
189
189
|
request: {
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
remoteIPAddress: request?.ip ?? '',
|
|
191
|
+
requestHeaders: request?.headers
|
|
192
|
+
? Object.fromEntries(
|
|
193
|
+
Object.entries(request.headers).map(([key, value]) => [
|
|
194
|
+
key,
|
|
195
|
+
Array.isArray(value) ? value : value !== undefined ? [value] : []
|
|
196
|
+
])
|
|
197
|
+
)
|
|
198
|
+
: {},
|
|
199
|
+
webhookUrl: request?.url?.split('?')[0],
|
|
200
|
+
httpMethod: request?.method,
|
|
201
|
+
rawQueryString: request?.url?.includes('?')
|
|
202
|
+
? request.url.substring(request.url.indexOf('?'))
|
|
203
|
+
: '',
|
|
204
|
+
httpReferrer: request?.headers?.referer as string | undefined,
|
|
205
|
+
httpUserAgent: request?.headers?.['user-agent'] as string | undefined,
|
|
206
|
+
service: '',
|
|
207
|
+
action: ''
|
|
192
208
|
},
|
|
193
209
|
user,
|
|
194
210
|
environment: {
|
|
@@ -19,7 +19,7 @@ export interface GenerateContextParams {
|
|
|
19
19
|
request?: ContextRequest
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id">
|
|
22
|
+
type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id" | "headers">
|
|
23
23
|
export interface GenerateContextDataParams extends Omit<GenerateContextParams, 'args'> {
|
|
24
24
|
GenerateContext: (params: GenerateContextParams) => Promise<unknown>
|
|
25
25
|
GenerateContextSync: (params: GenerateContextParams) => unknown
|
package/tsconfig.json
CHANGED
|
@@ -8,13 +8,8 @@
|
|
|
8
8
|
"declarationMap": true,
|
|
9
9
|
"noImplicitAny": true,
|
|
10
10
|
"strict": true,
|
|
11
|
-
"moduleResolution": "node",
|
|
12
11
|
"esModuleInterop": true,
|
|
13
12
|
"skipLibCheck": true,
|
|
14
|
-
"baseUrl": ".",
|
|
15
|
-
"paths": {
|
|
16
|
-
"*": ["../../node_modules/*"]
|
|
17
|
-
},
|
|
18
13
|
"lib": ["ES2021", "DOM"]
|
|
19
14
|
},
|
|
20
15
|
"include": ["src/**/*"],
|