@flowerforce/flowerbase 1.2.1-beta.2 → 1.2.1-beta.21

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.
Files changed (103) hide show
  1. package/README.md +37 -6
  2. package/dist/auth/controller.d.ts.map +1 -1
  3. package/dist/auth/controller.js +55 -4
  4. package/dist/auth/plugins/jwt.d.ts.map +1 -1
  5. package/dist/auth/plugins/jwt.js +52 -6
  6. package/dist/auth/providers/anon-user/controller.d.ts +8 -0
  7. package/dist/auth/providers/anon-user/controller.d.ts.map +1 -0
  8. package/dist/auth/providers/anon-user/controller.js +90 -0
  9. package/dist/auth/providers/anon-user/dtos.d.ts +10 -0
  10. package/dist/auth/providers/anon-user/dtos.d.ts.map +1 -0
  11. package/dist/auth/providers/anon-user/dtos.js +2 -0
  12. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  13. package/dist/auth/providers/custom-function/controller.js +35 -25
  14. package/dist/auth/providers/custom-function/dtos.d.ts +4 -1
  15. package/dist/auth/providers/custom-function/dtos.d.ts.map +1 -1
  16. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  17. package/dist/auth/providers/local-userpass/controller.js +159 -73
  18. package/dist/auth/providers/local-userpass/dtos.d.ts +17 -2
  19. package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
  20. package/dist/auth/utils.d.ts +76 -14
  21. package/dist/auth/utils.d.ts.map +1 -1
  22. package/dist/auth/utils.js +55 -61
  23. package/dist/constants.d.ts +12 -0
  24. package/dist/constants.d.ts.map +1 -1
  25. package/dist/constants.js +16 -4
  26. package/dist/features/functions/controller.d.ts.map +1 -1
  27. package/dist/features/functions/controller.js +31 -12
  28. package/dist/features/functions/dtos.d.ts +3 -0
  29. package/dist/features/functions/dtos.d.ts.map +1 -1
  30. package/dist/features/functions/interface.d.ts +3 -0
  31. package/dist/features/functions/interface.d.ts.map +1 -1
  32. package/dist/features/functions/utils.d.ts +3 -2
  33. package/dist/features/functions/utils.d.ts.map +1 -1
  34. package/dist/features/functions/utils.js +19 -7
  35. package/dist/features/triggers/index.d.ts.map +1 -1
  36. package/dist/features/triggers/index.js +49 -7
  37. package/dist/features/triggers/interface.d.ts +1 -0
  38. package/dist/features/triggers/interface.d.ts.map +1 -1
  39. package/dist/features/triggers/utils.d.ts.map +1 -1
  40. package/dist/features/triggers/utils.js +67 -26
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +48 -13
  43. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  44. package/dist/services/mongodb-atlas/index.js +72 -2
  45. package/dist/services/mongodb-atlas/model.d.ts +3 -2
  46. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  47. package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
  48. package/dist/services/mongodb-atlas/utils.js +3 -1
  49. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  50. package/dist/shared/handleUserRegistration.js +66 -1
  51. package/dist/shared/models/handleUserRegistration.model.d.ts +2 -1
  52. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  53. package/dist/shared/models/handleUserRegistration.model.js +1 -0
  54. package/dist/utils/context/helpers.d.ts +6 -6
  55. package/dist/utils/context/helpers.d.ts.map +1 -1
  56. package/dist/utils/context/index.d.ts +1 -1
  57. package/dist/utils/context/index.d.ts.map +1 -1
  58. package/dist/utils/context/index.js +176 -9
  59. package/dist/utils/context/interface.d.ts +1 -1
  60. package/dist/utils/context/interface.d.ts.map +1 -1
  61. package/dist/utils/crypto/index.d.ts +1 -0
  62. package/dist/utils/crypto/index.d.ts.map +1 -1
  63. package/dist/utils/crypto/index.js +6 -2
  64. package/dist/utils/initializer/exposeRoutes.js +1 -1
  65. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  66. package/dist/utils/initializer/registerPlugins.js +12 -4
  67. package/dist/utils/roles/helpers.js +2 -1
  68. package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
  69. package/dist/utils/rules-matcher/utils.js +3 -0
  70. package/package.json +1 -2
  71. package/src/auth/controller.ts +71 -5
  72. package/src/auth/plugins/jwt.test.ts +93 -0
  73. package/src/auth/plugins/jwt.ts +67 -8
  74. package/src/auth/providers/anon-user/controller.ts +91 -0
  75. package/src/auth/providers/anon-user/dtos.ts +10 -0
  76. package/src/auth/providers/custom-function/controller.ts +40 -31
  77. package/src/auth/providers/custom-function/dtos.ts +5 -1
  78. package/src/auth/providers/local-userpass/controller.ts +211 -101
  79. package/src/auth/providers/local-userpass/dtos.ts +20 -2
  80. package/src/auth/utils.ts +66 -83
  81. package/src/constants.ts +14 -2
  82. package/src/features/functions/controller.ts +42 -12
  83. package/src/features/functions/dtos.ts +3 -0
  84. package/src/features/functions/interface.ts +3 -0
  85. package/src/features/functions/utils.ts +29 -8
  86. package/src/features/triggers/index.ts +44 -1
  87. package/src/features/triggers/interface.ts +1 -0
  88. package/src/features/triggers/utils.ts +89 -37
  89. package/src/index.ts +49 -13
  90. package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
  91. package/src/services/mongodb-atlas/index.ts +665 -567
  92. package/src/services/mongodb-atlas/model.ts +16 -3
  93. package/src/services/mongodb-atlas/utils.ts +3 -0
  94. package/src/shared/handleUserRegistration.ts +83 -2
  95. package/src/shared/models/handleUserRegistration.model.ts +2 -1
  96. package/src/utils/__tests__/registerPlugins.test.ts +5 -1
  97. package/src/utils/context/index.ts +238 -18
  98. package/src/utils/context/interface.ts +1 -1
  99. package/src/utils/crypto/index.ts +5 -1
  100. package/src/utils/initializer/exposeRoutes.ts +1 -1
  101. package/src/utils/initializer/registerPlugins.ts +8 -0
  102. package/src/utils/roles/helpers.ts +3 -2
  103. package/src/utils/rules-matcher/utils.ts +3 -0
@@ -34,6 +34,13 @@ const logFunctionCall = (method: string, user: Record<string, any> | undefined,
34
34
  console.log('[functions-debug]', method, user ? { id: user.id, role: user.role, email: user.email } : 'no-user', args)
35
35
  }
36
36
 
37
+ const formatFunctionExecutionError = (error: unknown) => {
38
+ const err = error as { message?: string; name?: string }
39
+ const message = typeof err?.message === 'string' ? err.message : String(error)
40
+ const name = typeof err?.name === 'string' ? err.name : 'Error'
41
+ return JSON.stringify({ message, name })
42
+ }
43
+
37
44
  /**
38
45
  * > Creates a pre handler for every query
39
46
  * @param app -> the fastify instance
@@ -61,7 +68,18 @@ export const functionsController: FunctionController = async (
61
68
  if (!serviceFn) {
62
69
  throw new Error(`Service "${req.body.service}" does not exist`)
63
70
  }
64
- const [{ database, collection, query, update, document, documents, pipeline = [] }] = args
71
+ const [{
72
+ database,
73
+ collection,
74
+ query,
75
+ filter,
76
+ update,
77
+ options,
78
+ returnNewDocument,
79
+ document,
80
+ documents,
81
+ pipeline = []
82
+ }] = args
65
83
 
66
84
  const currentMethod = serviceFn(app, { rules, user })
67
85
  .db(database)
@@ -71,7 +89,10 @@ export const functionsController: FunctionController = async (
71
89
  const operatorsByType = await executeQuery({
72
90
  currentMethod,
73
91
  query,
92
+ filter,
74
93
  update,
94
+ options,
95
+ returnNewDocument,
75
96
  document,
76
97
  documents,
77
98
  pipeline,
@@ -91,17 +112,26 @@ export const functionsController: FunctionController = async (
91
112
  }
92
113
 
93
114
  logFunctionCall(`function:${method}`, user, args)
94
- const result = await GenerateContext({
95
- args: req.body.arguments,
96
- app,
97
- rules,
98
- user: { ...user, _id: new ObjectId(user.id) },
99
- currentFunction,
100
- functionsList,
101
- services
102
- })
103
- res.type('application/json')
104
- return JSON.stringify(result)
115
+ try {
116
+ const result = await GenerateContext({
117
+ args: req.body.arguments,
118
+ app,
119
+ rules,
120
+ user: { ...user, _id: new ObjectId(user.id) },
121
+ currentFunction,
122
+ functionsList,
123
+ services
124
+ })
125
+ res.type('application/json')
126
+ return JSON.stringify(result)
127
+ } catch (error) {
128
+ res.status(500)
129
+ res.type('application/json')
130
+ return JSON.stringify({
131
+ error: formatFunctionExecutionError(error),
132
+ error_code: 'FunctionExecutionError'
133
+ })
134
+ }
105
135
  })
106
136
  app.get<{
107
137
  Querystring: FunctionCallBase64Dto
@@ -23,8 +23,11 @@ export type FunctionCallBase64Dto = {
23
23
  type ArgumentsData = Arguments<{
24
24
  database: string
25
25
  collection: string
26
+ filter?: Document
26
27
  query: Parameters<GetOperatorsFunction>
27
28
  update: Document
29
+ options?: Document
30
+ returnNewDocument?: boolean
28
31
  document: Document
29
32
  documents: Document[]
30
33
  pipeline?: Document[]
@@ -24,6 +24,9 @@ export type ExecuteQueryParams = {
24
24
  currentMethod: ReturnType<GetOperatorsFunction>[keyof ReturnType<GetOperatorsFunction>]
25
25
  query: Parameters<GetOperatorsFunction>
26
26
  update: Document
27
+ filter?: Document
28
+ options?: Document
29
+ returnNewDocument?: boolean
27
30
  document: Document
28
31
  documents: Document[]
29
32
  pipeline: Document[]
@@ -44,29 +44,51 @@ export const executeQuery = async ({
44
44
  currentMethod,
45
45
  query,
46
46
  update,
47
+ filter,
48
+ options,
49
+ returnNewDocument,
47
50
  document,
48
51
  documents,
49
52
  pipeline,
50
53
  isClient = false
51
54
  }: ExecuteQueryParams) => {
55
+ const resolvedQuery =
56
+ typeof query !== 'undefined'
57
+ ? query
58
+ : typeof filter !== 'undefined'
59
+ ? filter
60
+ : {}
61
+ const resolvedUpdate = typeof update !== 'undefined' ? update : {}
62
+ const resolvedOptions =
63
+ typeof options !== 'undefined'
64
+ ? options
65
+ : typeof returnNewDocument === 'boolean'
66
+ ? { returnDocument: returnNewDocument ? 'after' : 'before' }
67
+ : undefined
52
68
  return {
53
69
  find: async () =>
54
70
  await (currentMethod as ReturnType<GetOperatorsFunction>['find'])(
55
- EJSON.deserialize(query)
71
+ EJSON.deserialize(resolvedQuery)
56
72
  ).toArray(),
57
73
  findOne: () =>
58
74
  (currentMethod as ReturnType<GetOperatorsFunction>['findOne'])(
59
- EJSON.deserialize(query)
75
+ EJSON.deserialize(resolvedQuery)
60
76
  ),
61
77
  deleteOne: () =>
62
78
  (currentMethod as ReturnType<GetOperatorsFunction>['deleteOne'])(
63
- EJSON.deserialize(query)
79
+ EJSON.deserialize(resolvedQuery)
64
80
  ),
65
81
  insertOne: () =>
66
82
  (currentMethod as ReturnType<GetOperatorsFunction>['insertOne'])(
67
83
  EJSON.deserialize(document)
68
84
  ),
69
- updateOne: () => currentMethod(EJSON.deserialize(query), EJSON.deserialize(update)),
85
+ updateOne: () => currentMethod(EJSON.deserialize(resolvedQuery), EJSON.deserialize(resolvedUpdate)),
86
+ findOneAndUpdate: () =>
87
+ (currentMethod as ReturnType<GetOperatorsFunction>['findOneAndUpdate'])(
88
+ EJSON.deserialize(resolvedQuery),
89
+ EJSON.deserialize(resolvedUpdate),
90
+ resolvedOptions ? EJSON.deserialize(resolvedOptions) : undefined
91
+ ),
70
92
  aggregate: async () =>
71
93
  (await (currentMethod as ReturnType<GetOperatorsFunction>['aggregate'])(
72
94
  EJSON.deserialize(pipeline),
@@ -79,13 +101,12 @@ export const executeQuery = async ({
79
101
  ),
80
102
  updateMany: () =>
81
103
  (currentMethod as ReturnType<GetOperatorsFunction>['updateMany'])(
82
- EJSON.deserialize(query),
83
- EJSON.deserialize(update)
104
+ EJSON.deserialize(resolvedQuery),
105
+ EJSON.deserialize(resolvedUpdate)
84
106
  ),
85
107
  deleteMany: () =>
86
108
  (currentMethod as ReturnType<GetOperatorsFunction>['deleteMany'])(
87
- EJSON.deserialize(query)
109
+ EJSON.deserialize(resolvedQuery)
88
110
  )
89
111
  }
90
112
  }
91
-
@@ -1,3 +1,4 @@
1
+ import { AUTH_CONFIG, DB_NAME } from '../../constants'
1
2
  import { services } from '../../services'
2
3
  import { Function, Functions } from '../functions/interface'
3
4
  import { ActivateTriggersParams } from './dtos'
@@ -17,7 +18,49 @@ export const activateTriggers = async ({
17
18
  }: ActivateTriggersParams) => {
18
19
  console.log('START ACTIVATION TRIGGERS')
19
20
  try {
20
- for await (const trigger of triggersList) {
21
+ const triggersToActivate = [...triggersList]
22
+ if (AUTH_CONFIG.on_user_creation_function_name) {
23
+ const alreadyDeclared = triggersToActivate.some(
24
+ (trigger) =>
25
+ trigger.content.type === 'AUTHENTICATION' &&
26
+ trigger.content.event_processors?.FUNCTION?.config?.function_name ===
27
+ AUTH_CONFIG.on_user_creation_function_name
28
+ )
29
+ if (!alreadyDeclared) {
30
+ triggersToActivate.push({
31
+ fileName: '__auto_on_user_creation_trigger__.json',
32
+ content: {
33
+ name: 'onUserCreation',
34
+ type: 'AUTHENTICATION',
35
+ disabled: false,
36
+ config: {
37
+ isAutoTrigger: true,
38
+ collection: AUTH_CONFIG.authCollection ?? 'auth_users',
39
+ database: DB_NAME,
40
+ full_document: true,
41
+ full_document_before_change: false,
42
+ match: {},
43
+ operation_types: ['insert', 'update', 'replace'],
44
+ project: {},
45
+ service_name: 'mongodb-atlas',
46
+ skip_catchup_events: false,
47
+ tolerate_resume_errors: false,
48
+ unordered: false,
49
+ schedule: ''
50
+ },
51
+ event_processors: {
52
+ FUNCTION: {
53
+ config: {
54
+ function_name: AUTH_CONFIG.on_user_creation_function_name
55
+ }
56
+ }
57
+ }
58
+ }
59
+ })
60
+ }
61
+ }
62
+
63
+ for await (const trigger of triggersToActivate) {
21
64
  const { content } = trigger
22
65
  const { type, config, event_processors } = content
23
66
 
@@ -21,6 +21,7 @@ type Config = {
21
21
  database: string
22
22
  full_document: boolean
23
23
  full_document_before_change: boolean
24
+ isAutoTrigger?: boolean
24
25
  match: Record<string, unknown>
25
26
  operation_types: string[]
26
27
  project: Record<string, unknown>
@@ -110,55 +110,107 @@ const handleAuthenticationTrigger = async ({
110
110
  services,
111
111
  app
112
112
  }: HandlerParams) => {
113
- const { database } = config
113
+ const { database, isAutoTrigger } = config
114
+ const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
115
+ const collection = app.mongo.client.db(database || DB_NAME).collection(authCollection)
114
116
  const pipeline = [
115
117
  {
116
118
  $match: {
117
- operationType: { $in: ['insert'] }
119
+ operationType: { $in: ['insert', 'update', 'replace'] }
118
120
  }
119
121
  }
120
122
  ]
121
- const changeStream = app.mongo.client
122
- .db(database || DB_NAME)
123
- .collection(AUTH_CONFIG.authCollection)
124
- .watch(pipeline, {
125
- fullDocument: 'whenAvailable'
126
- })
123
+ const changeStream = collection.watch(pipeline, {
124
+ fullDocument: 'whenAvailable'
125
+ })
127
126
  changeStream.on('error', (error) => {
128
127
  if (shouldIgnoreStreamError(error)) return
129
128
  console.error('Authentication trigger change stream error', error)
130
129
  })
131
130
  changeStream.on('change', async function (change) {
132
- const document = change['fullDocument' as keyof typeof change] as Record<
133
- string,
134
- string
135
- > //TODO -> define user type
136
-
137
- if (document) {
138
- delete document.password
139
-
140
- const currentUser = { ...document }
141
- delete currentUser.password
142
- await GenerateContext({
143
- args: [{
144
- user: {
145
- ...currentUser,
146
- id: currentUser._id.toString(),
147
- data: {
148
- _id: currentUser._id.toString(),
149
- email: currentUser.email
150
- }
151
- }
152
- }],
153
- app,
154
- rules: StateManager.select("rules"),
155
- user: {}, // TODO from currentUser ??
156
- currentFunction: triggerHandler,
157
- functionsList,
158
- services,
159
- runAsSystem: true
160
- })
131
+ const operationType = change['operationType' as keyof typeof change] as string | undefined
132
+ const documentKey = change['documentKey' as keyof typeof change] as
133
+ | { _id?: unknown }
134
+ | undefined
135
+ const fullDocument = change['fullDocument' as keyof typeof change] as
136
+ | Record<string, unknown>
137
+ | null
138
+ if (!documentKey?._id) {
139
+ return
161
140
  }
141
+
142
+ const updateDescription = change[
143
+ 'updateDescription' as keyof typeof change
144
+ ] as { updatedFields?: Record<string, unknown> } | undefined
145
+ const updatedStatus = updateDescription?.updatedFields?.status
146
+ let confirmedCandidate = false
147
+ let confirmedDocument =
148
+ fullDocument as Record<string, unknown> | null
149
+
150
+ if (operationType === 'update') {
151
+ if (updatedStatus === 'confirmed') {
152
+ confirmedCandidate = true
153
+ } else if (updatedStatus === undefined) {
154
+ const fetched = await collection.findOne({
155
+ _id: documentKey._id
156
+ }) as Record<string, unknown> | null
157
+ confirmedDocument = fetched ?? confirmedDocument
158
+ confirmedCandidate = (confirmedDocument as { status?: string } | null)?.status === 'confirmed'
159
+ }
160
+ } else {
161
+ confirmedCandidate = (confirmedDocument as { status?: string } | null)?.status === 'confirmed'
162
+ }
163
+
164
+ if (!confirmedCandidate) {
165
+ return
166
+ }
167
+
168
+ const updateResult = await collection.findOneAndUpdate(
169
+ {
170
+ _id: documentKey._id,
171
+ status: 'confirmed',
172
+ on_user_creation_triggered_at: { $exists: false }
173
+ },
174
+ {
175
+ $set: {
176
+ on_user_creation_triggered_at: new Date()
177
+ }
178
+ },
179
+ {
180
+ returnDocument: 'after'
181
+ }
182
+ )
183
+
184
+ const document =
185
+ (updateResult?.value as Record<string, unknown> | null) ?? confirmedDocument
186
+ if (!document) {
187
+ return
188
+ }
189
+
190
+ delete (document as { password?: unknown }).password
191
+
192
+ const currentUser = { ...document }
193
+ delete (currentUser as { password?: unknown }).password
194
+
195
+ const userData = {
196
+ ...currentUser,
197
+ id: (currentUser as { _id: { toString: () => string } })._id.toString(),
198
+ data: {
199
+ _id: (currentUser as { _id: { toString: () => string } })._id.toString(),
200
+ email: (currentUser as { email?: string }).email
201
+ }
202
+ }
203
+ // TODO change va ripulito
204
+ await GenerateContext({
205
+ args: isAutoTrigger ? [userData] : [{ user: userData /*, ...change */ }],
206
+ app,
207
+ rules: StateManager.select("rules"),
208
+ user: {}, // TODO from currentUser ??
209
+ currentFunction: triggerHandler,
210
+ functionsList,
211
+ services,
212
+ runAsSystem: true
213
+ })
162
214
  })
163
215
  registerOnClose(
164
216
  app,
package/src/index.ts CHANGED
@@ -49,24 +49,34 @@ export async function initialize({
49
49
  corsConfig = DEFAULT_CONFIG.CORS_OPTIONS,
50
50
  basePath
51
51
  }: InitializeConfig) {
52
+ if (!jwtSecret || jwtSecret.trim().length === 0) {
53
+ throw new Error('JWT secret missing: set JWT_SECRET or pass jwtSecret to initialize()')
54
+ }
55
+
52
56
  const resolvedBasePath = basePath ?? require.main?.path ?? process.cwd()
53
57
  const fastify = Fastify({
54
58
  logger: !!DEFAULT_CONFIG.ENABLE_LOGGER
55
59
  })
56
60
 
57
- console.log("BASE PATH", resolvedBasePath)
61
+ const isTest = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined
62
+ const logInfo = (...args: unknown[]) => {
63
+ if (!isTest) {
64
+ console.log(...args)
65
+ }
66
+ }
58
67
 
59
- console.log("CURRENT PORT", port)
60
- console.log("CURRENT HOST", host)
68
+ logInfo("BASE PATH", resolvedBasePath)
69
+ logInfo("CURRENT PORT", port)
70
+ logInfo("CURRENT HOST", host)
61
71
 
62
72
  const functionsList = await loadFunctions(resolvedBasePath)
63
- console.log("Functions LOADED")
73
+ logInfo("Functions LOADED")
64
74
  const triggersList = await loadTriggers(resolvedBasePath)
65
- console.log("Triggers LOADED")
75
+ logInfo("Triggers LOADED")
66
76
  const endpointsList = await loadEndpoints(resolvedBasePath)
67
- console.log("Endpoints LOADED")
77
+ logInfo("Endpoints LOADED")
68
78
  const rulesList = await loadRules(resolvedBasePath)
69
- console.log("Rules LOADED")
79
+ logInfo("Rules LOADED")
70
80
 
71
81
  const stateConfig = {
72
82
  functions: functionsList,
@@ -90,7 +100,33 @@ export async function initialize({
90
100
  deepLinking: false
91
101
  },
92
102
  uiHooks: {
93
- onRequest: function (request, reply, next) { next() },
103
+ onRequest: function (request, reply, next) {
104
+ const swaggerUser = DEFAULT_CONFIG.SWAGGER_UI_USER
105
+ const swaggerPassword = DEFAULT_CONFIG.SWAGGER_UI_PASSWORD
106
+ if (!swaggerUser && !swaggerPassword) {
107
+ next()
108
+ return
109
+ }
110
+ const authHeader = request.headers.authorization
111
+ if (!authHeader || !authHeader.startsWith('Basic ')) {
112
+ reply
113
+ .code(401)
114
+ .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
115
+ .send({ message: 'Unauthorized' })
116
+ return
117
+ }
118
+ const encoded = authHeader.slice('Basic '.length)
119
+ const decoded = Buffer.from(encoded, 'base64').toString('utf8')
120
+ const [user, pass] = decoded.split(':')
121
+ if (user !== swaggerUser || pass !== swaggerPassword) {
122
+ reply
123
+ .code(401)
124
+ .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
125
+ .send({ message: 'Unauthorized' })
126
+ return
127
+ }
128
+ next()
129
+ },
94
130
  preHandler: function (request, reply, next) { next() }
95
131
  },
96
132
  staticCSP: true,
@@ -107,15 +143,15 @@ export async function initialize({
107
143
  corsConfig
108
144
  })
109
145
 
110
- console.log('Plugins registration COMPLETED')
146
+ logInfo('Plugins registration COMPLETED')
111
147
  await exposeRoutes(fastify)
112
- console.log('APP Routes registration COMPLETED')
148
+ logInfo('APP Routes registration COMPLETED')
113
149
  await registerFunctions({ app: fastify, functionsList, rulesList })
114
- console.log('Functions registration COMPLETED')
150
+ logInfo('Functions registration COMPLETED')
115
151
  await generateEndpoints({ app: fastify, functionsList, endpointsList, rulesList })
116
- console.log('HTTP Endpoints registration COMPLETED')
152
+ logInfo('HTTP Endpoints registration COMPLETED')
117
153
  fastify.ready(() => {
118
- console.log("FASTIFY IS READY")
154
+ logInfo("FASTIFY IS READY")
119
155
  if (triggersList?.length > 0) activateTriggers({ fastify, triggersList, functionsList })
120
156
  })
121
157
  await fastify.listen({ port, host })
@@ -0,0 +1,95 @@
1
+ import { Document, ObjectId } from 'mongodb'
2
+ import MongoDbAtlas from '..'
3
+ import { Role, Rules } from '../../../features/rules/interface'
4
+
5
+ const createAppWithCollection = (collection: Record<string, unknown>) => ({
6
+ mongo: {
7
+ client: {
8
+ db: jest.fn().mockReturnValue({
9
+ collection: jest.fn().mockReturnValue(collection)
10
+ })
11
+ }
12
+ }
13
+ })
14
+
15
+ const createRules = (roleOverrides: Partial<Role> = {}): Rules => ({
16
+ todos: {
17
+ database: 'db',
18
+ collection: 'todos',
19
+ filters: [],
20
+ roles: [
21
+ {
22
+ name: 'owner',
23
+ apply_when: {},
24
+ insert: true,
25
+ delete: true,
26
+ search: true,
27
+ read: true,
28
+ write: true,
29
+ ...roleOverrides
30
+ }
31
+ ]
32
+ }
33
+ })
34
+
35
+ describe('mongodb-atlas findOneAndUpdate', () => {
36
+ it('applies write/read validation and returns the updated document', async () => {
37
+ const id = new ObjectId()
38
+ const existingDoc = { _id: id, title: 'Old', userId: 'user-1' }
39
+ const updatedDoc = { _id: id, title: 'New', userId: 'user-1' }
40
+ const findOne = jest.fn().mockResolvedValue(existingDoc)
41
+ const aggregate = jest.fn().mockReturnValue({
42
+ toArray: jest.fn().mockResolvedValue([updatedDoc])
43
+ })
44
+ const findOneAndUpdate = jest.fn().mockResolvedValue(updatedDoc)
45
+ const collection = {
46
+ collectionName: 'todos',
47
+ findOne,
48
+ aggregate,
49
+ findOneAndUpdate
50
+ }
51
+
52
+ const app = createAppWithCollection(collection)
53
+ const operators = MongoDbAtlas(app as any, {
54
+ rules: createRules(),
55
+ user: { id: 'user-1' }
56
+ })
57
+ .db('db')
58
+ .collection('todos')
59
+
60
+ const result = await operators.findOneAndUpdate({ _id: id }, { $set: { title: 'New' } })
61
+
62
+ expect(findOne).toHaveBeenCalled()
63
+ expect(aggregate).toHaveBeenCalled()
64
+ expect(findOneAndUpdate).toHaveBeenCalledWith(
65
+ { $and: [{ _id: id }] },
66
+ { $set: { title: 'New' } }
67
+ )
68
+ expect(result).toEqual(updatedDoc)
69
+ })
70
+
71
+ it('rejects updates when write permission is denied', async () => {
72
+ const id = new ObjectId()
73
+ const existingDoc = { _id: id, title: 'Old', userId: 'user-1' }
74
+ const findOne = jest.fn().mockResolvedValue(existingDoc)
75
+ const findOneAndUpdate = jest.fn()
76
+ const collection = {
77
+ collectionName: 'todos',
78
+ findOne,
79
+ findOneAndUpdate
80
+ }
81
+
82
+ const app = createAppWithCollection(collection)
83
+ const operators = MongoDbAtlas(app as any, {
84
+ rules: createRules({ write: false }),
85
+ user: { id: 'user-1' }
86
+ })
87
+ .db('db')
88
+ .collection('todos')
89
+
90
+ await expect(
91
+ operators.findOneAndUpdate({ _id: id }, { title: 'Denied' } as Document)
92
+ ).rejects.toThrow('Update not permitted')
93
+ expect(findOneAndUpdate).not.toHaveBeenCalled()
94
+ })
95
+ })