@flowerforce/flowerbase 1.2.0 → 1.2.1-beta.2

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 (91) hide show
  1. package/dist/auth/controller.d.ts.map +1 -1
  2. package/dist/auth/controller.js +3 -0
  3. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  4. package/dist/auth/providers/custom-function/controller.js +5 -2
  5. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  6. package/dist/auth/providers/local-userpass/controller.js +7 -10
  7. package/dist/auth/utils.d.ts.map +1 -1
  8. package/dist/auth/utils.js +3 -2
  9. package/dist/constants.d.ts +5 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +5 -1
  12. package/dist/features/functions/controller.d.ts.map +1 -1
  13. package/dist/features/functions/controller.js +28 -2
  14. package/dist/features/rules/utils.d.ts.map +1 -1
  15. package/dist/features/rules/utils.js +11 -2
  16. package/dist/features/triggers/utils.d.ts.map +1 -1
  17. package/dist/features/triggers/utils.js +52 -2
  18. package/dist/index.d.ts +8 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +10 -9
  21. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  22. package/dist/services/mongodb-atlas/index.js +540 -483
  23. package/dist/services/mongodb-atlas/utils.d.ts +9 -2
  24. package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
  25. package/dist/services/mongodb-atlas/utils.js +113 -23
  26. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  27. package/dist/shared/handleUserRegistration.js +1 -0
  28. package/dist/shared/models/handleUserRegistration.model.d.ts +6 -2
  29. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  30. package/dist/utils/context/helpers.d.ts +6 -5
  31. package/dist/utils/context/helpers.d.ts.map +1 -1
  32. package/dist/utils/context/helpers.js +3 -0
  33. package/dist/utils/context/index.d.ts.map +1 -1
  34. package/dist/utils/context/index.js +2 -0
  35. package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -1
  36. package/dist/utils/initializer/exposeRoutes.js +11 -4
  37. package/dist/utils/initializer/registerPlugins.d.ts +3 -1
  38. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  39. package/dist/utils/initializer/registerPlugins.js +9 -6
  40. package/dist/utils/roles/helpers.js +9 -2
  41. package/dist/utils/roles/machines/commonValidators.d.ts.map +1 -1
  42. package/dist/utils/roles/machines/commonValidators.js +10 -6
  43. package/dist/utils/roles/machines/read/B/validators.d.ts +4 -0
  44. package/dist/utils/roles/machines/read/B/validators.d.ts.map +1 -0
  45. package/dist/utils/roles/machines/read/B/validators.js +8 -0
  46. package/dist/utils/roles/machines/read/C/index.d.ts.map +1 -1
  47. package/dist/utils/roles/machines/read/C/index.js +10 -7
  48. package/dist/utils/roles/machines/read/C/validators.d.ts +5 -0
  49. package/dist/utils/roles/machines/read/C/validators.d.ts.map +1 -0
  50. package/dist/utils/roles/machines/read/C/validators.js +29 -0
  51. package/dist/utils/roles/machines/read/D/index.d.ts.map +1 -1
  52. package/dist/utils/roles/machines/read/D/index.js +13 -11
  53. package/dist/utils/rules.d.ts +1 -1
  54. package/dist/utils/rules.d.ts.map +1 -1
  55. package/dist/utils/rules.js +26 -17
  56. package/jest.config.ts +2 -12
  57. package/jest.setup.ts +28 -0
  58. package/package.json +1 -1
  59. package/src/auth/controller.ts +3 -0
  60. package/src/auth/providers/custom-function/controller.ts +5 -2
  61. package/src/auth/providers/local-userpass/controller.ts +13 -10
  62. package/src/auth/utils.ts +6 -3
  63. package/src/constants.ts +7 -2
  64. package/src/fastify.d.ts +32 -15
  65. package/src/features/functions/controller.ts +36 -2
  66. package/src/features/rules/utils.ts +11 -2
  67. package/src/features/triggers/utils.ts +59 -2
  68. package/src/index.ts +21 -8
  69. package/src/services/mongodb-atlas/__tests__/utils.test.ts +141 -0
  70. package/src/services/mongodb-atlas/index.ts +143 -90
  71. package/src/services/mongodb-atlas/utils.ts +158 -22
  72. package/src/shared/handleUserRegistration.ts +3 -3
  73. package/src/shared/models/handleUserRegistration.model.ts +8 -3
  74. package/src/types/fastify-raw-body.d.ts +22 -0
  75. package/src/utils/__tests__/STEP_B_STATES.test.ts +1 -1
  76. package/src/utils/__tests__/STEP_C_STATES.test.ts +1 -1
  77. package/src/utils/__tests__/STEP_D_STATES.test.ts +2 -2
  78. package/src/utils/__tests__/checkIsValidFieldNameFn.test.ts +9 -4
  79. package/src/utils/__tests__/registerPlugins.test.ts +16 -1
  80. package/src/utils/context/helpers.ts +3 -0
  81. package/src/utils/context/index.ts +1 -0
  82. package/src/utils/initializer/exposeRoutes.ts +15 -8
  83. package/src/utils/initializer/registerPlugins.ts +15 -7
  84. package/src/utils/roles/helpers.ts +20 -3
  85. package/src/utils/roles/machines/commonValidators.ts +10 -5
  86. package/src/utils/roles/machines/read/B/validators.ts +8 -0
  87. package/src/utils/roles/machines/read/C/index.ts +11 -7
  88. package/src/utils/roles/machines/read/C/validators.ts +21 -0
  89. package/src/utils/roles/machines/read/D/index.ts +22 -12
  90. package/src/utils/rules.ts +31 -22
  91. package/tsconfig.spec.json +7 -0
@@ -12,6 +12,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.STEP_D_STATES = void 0;
13
13
  const utils_1 = require("../../utils");
14
14
  const validators_1 = require("./validators");
15
+ const runCheckIsValidFieldName = (_a) => __awaiter(void 0, [_a], void 0, function* ({ context, endValidation }) {
16
+ (0, utils_1.logMachineInfo)({
17
+ enabled: context.enableLog,
18
+ machine: 'D',
19
+ step: 2,
20
+ stepName: 'checkIsValidFieldName'
21
+ });
22
+ const document = (0, validators_1.checkIsValidFieldNameFn)(context);
23
+ return endValidation({ success: !!Object.keys(document).length, document });
24
+ });
15
25
  exports.STEP_D_STATES = {
16
26
  checkAdditionalFields: (_a) => __awaiter(void 0, [_a], void 0, function* ({ context, next, endValidation }) {
17
27
  (0, utils_1.logMachineInfo)({
@@ -21,16 +31,8 @@ exports.STEP_D_STATES = {
21
31
  stepName: 'checkAdditionalFields'
22
32
  });
23
33
  const check = (0, validators_1.checkAdditionalFieldsFn)(context);
24
- return check ? next('checkIsValidFieldName') : endValidation({ success: false });
34
+ return check ? next('evaluateRead') : endValidation({ success: false });
25
35
  }),
26
- checkIsValidFieldName: (_a) => __awaiter(void 0, [_a], void 0, function* ({ context, endValidation }) {
27
- (0, utils_1.logMachineInfo)({
28
- enabled: context.enableLog,
29
- machine: 'D',
30
- step: 2,
31
- stepName: 'checkIsValidFieldName'
32
- });
33
- const document = (0, validators_1.checkIsValidFieldNameFn)(context);
34
- return endValidation({ success: !!Object.keys(document).length, document });
35
- })
36
+ evaluateRead: runCheckIsValidFieldName,
37
+ checkIsValidFieldName: runCheckIsValidFieldName
36
38
  };
@@ -1,2 +1,2 @@
1
- export declare function expandQuery(template: Record<string, unknown>, objs: Record<string, unknown>): any;
1
+ export declare function expandQuery(template: Record<string, unknown>, objs: Record<string, unknown>): Record<string, unknown>;
2
2
  //# sourceMappingURL=rules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/utils/rules.ts"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAqB9B"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/utils/rules.ts"],"names":[],"mappings":"AAkCA,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAES,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC9D"}
@@ -5,23 +5,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.expandQuery = expandQuery;
7
7
  const get_1 = __importDefault(require("lodash/get"));
8
- const removeExtraColons = (val) => {
9
- return val === null || val === void 0 ? void 0 : val.toString().replace(/:+/g, ":");
8
+ const resolvePlaceholder = (value, objs) => {
9
+ if (!value.startsWith('%%'))
10
+ return value;
11
+ const path = value.slice(2);
12
+ const [rootKey, ...rest] = path.split('.');
13
+ const rootToken = `%%${rootKey}`;
14
+ const rootValue = objs[rootToken];
15
+ if (!rest.length) {
16
+ return rootValue === undefined ? value : rootValue;
17
+ }
18
+ const resolved = (0, get_1.default)(rootValue, rest.join('.'));
19
+ return resolved === undefined ? value : resolved;
10
20
  };
11
- // Funzione che espande dinamicamente i placeholder con supporto per percorsi annidati
21
+ const expandValue = (input, objs) => {
22
+ if (Array.isArray(input)) {
23
+ return input.map((item) => expandValue(item, objs));
24
+ }
25
+ if (input && typeof input === 'object') {
26
+ return Object.fromEntries(Object.entries(input).map(([key, val]) => [key, expandValue(val, objs)]));
27
+ }
28
+ if (typeof input === 'string') {
29
+ return resolvePlaceholder(input, objs);
30
+ }
31
+ return input;
32
+ };
33
+ // Espande dinamicamente i placeholder con supporto per array e percorsi annidati
12
34
  function expandQuery(template, objs) {
13
- let expandedQuery = JSON.stringify(template); // Converti l'oggetto in una stringa per sostituire i placeholder
14
- const regex = /:\s*"%%([a-zA-Z0-9_.]+)"/g;
15
- Object.keys(objs).forEach(() => {
16
- // Espandi tutti i placeholder %%values.<nested.property>
17
- const callback = (match, path) => {
18
- const value = (0, get_1.default)(objs, `%%${path}`); // Recupera il valore annidato da values
19
- const finalValue = typeof value === 'string' ? `"${value}"` : value && JSON.stringify(value);
20
- // TODO tolto i primi : creava questo tipo di oggetto {"userId"::"%%user.id"}
21
- const val = `:${value !== undefined ? finalValue : match}`; // Sostituisci se esiste, altrimenti lascia il placeholder
22
- return removeExtraColons(val);
23
- };
24
- expandedQuery = expandedQuery.replace(regex, callback);
25
- });
26
- return JSON.parse(expandedQuery); // Converti la stringa JSON di nuovo in un oggetto
35
+ return expandValue(template, objs);
27
36
  }
package/jest.config.ts CHANGED
@@ -4,21 +4,11 @@ module.exports = {
4
4
  '^.+\\.[tj]s$': [
5
5
  'ts-jest',
6
6
  {
7
- tsconfig: './tsconfig.json'
7
+ tsconfig: '<rootDir>/tsconfig.json'
8
8
  }
9
9
  ]
10
10
  },
11
- collectCoverage: false,
12
- collectCoverageFrom: ['./**/*.ts'],
13
- coverageDirectory: 'coverage',
14
- coverageThreshold: {
15
- global: {
16
- branches: 50,
17
- functions: 90,
18
- lines: 90,
19
- statements: 90
20
- }
21
- },
11
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
22
12
  testEnvironment: 'node',
23
13
  testMatch: ['./**/*.test.ts']
24
14
  }
package/jest.setup.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { Blob as NodeBlob } from 'buffer'
2
+ import path from 'node:path'
3
+
4
+ if (!process.env.FLOWERBASE_APP_PATH) {
5
+ process.env.FLOWERBASE_APP_PATH = path.resolve(__dirname, '../../tests/e2e/app')
6
+ }
7
+
8
+ const BaseBlob = typeof globalThis.Blob !== 'undefined' ? globalThis.Blob : NodeBlob
9
+
10
+ type PolyfillFilePropertyBag = FilePropertyBag & {
11
+ name?: string
12
+ }
13
+
14
+ class FilePolyfill extends BaseBlob {
15
+ lastModified: number
16
+ name: string
17
+
18
+ constructor(bits?: Iterable<BlobPart>, options?: FilePropertyBag) {
19
+ super(bits, options as FilePropertyBag)
20
+ const fileOptions = options as PolyfillFilePropertyBag
21
+ this.name = fileOptions?.name ?? ''
22
+ this.lastModified = fileOptions?.lastModified ?? Date.now()
23
+ }
24
+ }
25
+
26
+ if (typeof globalThis.File === 'undefined') {
27
+ globalThis.File = FilePolyfill as unknown as typeof File
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.2.0",
3
+ "version": "1.2.1-beta.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,6 +26,9 @@ export async function authController(app: FastifyInstance) {
26
26
  * @returns {Promise<Object>} A promise resolving with the user's profile data.
27
27
  */
28
28
  app.get(AUTH_ENDPOINTS.PROFILE, async function (req) {
29
+ if (req.user.typ !== 'access') {
30
+ throw new Error('Access token required')
31
+ }
29
32
  const user = await db
30
33
  .collection<Record<string, unknown>>(authCollection)
31
34
  .findOne({ _id: ObjectId.createFromHexString(req.user.id) })
@@ -77,18 +77,21 @@ export async function customFunctionController(app: FastifyInstance) {
77
77
 
78
78
  if (res.id) {
79
79
  const user = await handleUserRegistration(app, { run_as_system: true, skipUserCheck: true, provider: PROVIDER.CUSTOM_FUNCTION })({ email: res.id, password: generatePassword() })
80
+ if (!user?.insertedId) {
81
+ throw new Error('Failed to register custom user')
82
+ }
80
83
 
81
84
  const currentUserData = {
82
85
  _id: user.insertedId,
83
86
  user_data: {
84
- _id: user.insertedId,
87
+ _id: user.insertedId
85
88
  }
86
89
  }
87
90
  return {
88
91
  access_token: this.createAccessToken(currentUserData),
89
92
  refresh_token: this.createRefreshToken(currentUserData),
90
93
  device_id: '',
91
- user_id: user.insertedId.toString(),
94
+ user_id: user.insertedId.toString()
92
95
  }
93
96
  }
94
97
 
@@ -55,8 +55,13 @@ export async function localUserPassController(app: FastifyInstance) {
55
55
 
56
56
  const result = await handleUserRegistration(app, { run_as_system: true, provider: PROVIDER.LOCAL_USERPASS })({ email: req.body.email.toLowerCase(), password: req.body.password })
57
57
 
58
+ if (!result?.insertedId) {
59
+ res?.status(500)
60
+ throw new Error('Failed to register user')
61
+ }
62
+
58
63
  res?.status(201)
59
- return { userId: result?.insertedId.toString() }
64
+ return { userId: result.insertedId.toString() }
60
65
  }
61
66
  )
62
67
 
@@ -98,7 +103,12 @@ export async function localUserPassController(app: FastifyInstance) {
98
103
  : {}
99
104
  delete authUser?.password
100
105
 
101
- const userWithCustomData = { ...authUser, user_data: user, id: authUser._id.toString() }
106
+ const userWithCustomData = {
107
+ ...authUser,
108
+ user_data: { ...(user || {}), _id: authUser._id },
109
+ data: { email: authUser.email },
110
+ id: authUser._id.toString()
111
+ }
102
112
 
103
113
  if (authUser && authUser.status === 'pending') {
104
114
  try {
@@ -123,14 +133,7 @@ export async function localUserPassController(app: FastifyInstance) {
123
133
  ) {
124
134
  try {
125
135
  await GenerateContext({
126
- args: [
127
- {
128
- operationType: 'CREATE',
129
- providers: 'local-userpass',
130
- user: userWithCustomData,
131
- time: new Date().getTime()
132
- }
133
- ],
136
+ args: [userWithCustomData],
134
137
  app,
135
138
  rules: {},
136
139
  user: userWithCustomData,
package/src/auth/utils.ts CHANGED
@@ -115,13 +115,16 @@ export interface CustomUserDataConfig {
115
115
  on_user_creation_function_name: string
116
116
  }
117
117
 
118
+ const resolveAppPath = () =>
119
+ process.env.FLOWERBASE_APP_PATH ?? require.main?.path ?? process.cwd()
120
+
118
121
 
119
122
  /**
120
123
  * > Loads the auth config json file
121
124
  * @testable
122
125
  */
123
126
  export const loadAuthConfig = (): AuthConfig => {
124
- const authPath = path.join(require.main!.path, 'auth/providers.json')
127
+ const authPath = path.join(resolveAppPath(), 'auth/providers.json')
125
128
  return JSON.parse(fs.readFileSync(authPath, 'utf-8'))
126
129
  }
127
130
 
@@ -130,7 +133,7 @@ export const loadAuthConfig = (): AuthConfig => {
130
133
  * @testable
131
134
  */
132
135
  export const loadCustomUserData = (): CustomUserDataConfig => {
133
- const userDataPath = path.join(require.main!.path, 'auth/custom_user_data.json')
136
+ const userDataPath = path.join(resolveAppPath(), 'auth/custom_user_data.json')
134
137
  return JSON.parse(fs.readFileSync(userDataPath, 'utf-8'))
135
138
  }
136
139
 
@@ -205,4 +208,4 @@ export const getMailConfig = (
205
208
  export const generatePassword = (length = 20) => {
206
209
  const bytes = crypto.randomBytes(length);
207
210
  return Array.from(bytes, (b) => CHARSET[b % CHARSET.length]).join("");
208
- }
211
+ }
package/src/constants.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { loadAuthConfig, loadCustomUserData } from './auth/utils'
2
+ import { ALLOWED_METHODS } from './'
2
3
 
3
4
  const {
4
5
  database_name,
@@ -15,7 +16,11 @@ export const DEFAULT_CONFIG = {
15
16
  API_VERSION: process.env.API_VERSION || 'v2.0',
16
17
  HTTPS_SCHEMA: process.env.HTTPS_SCHEMA || 'https',
17
18
  HOST: process.env.HOST || '0.0.0.0',
18
- ENABLE_LOGGER: process.env.ENABLE_LOGGER
19
+ ENABLE_LOGGER: process.env.ENABLE_LOGGER,
20
+ CORS_OPTIONS: {
21
+ origin: "*",
22
+ methods: ["GET", "POST", "PUT", "DELETE"] as ALLOWED_METHODS[]
23
+ }
19
24
  }
20
25
  export const API_VERSION = `/api/client/${DEFAULT_CONFIG.API_VERSION}`
21
26
  export const HTTPS_SCHEMA = DEFAULT_CONFIG.HTTPS_SCHEMA
@@ -39,4 +44,4 @@ export const AUTH_CONFIG = {
39
44
  export const S3_CONFIG = {
40
45
  ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID,
41
46
  SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY
42
- }
47
+ }
package/src/fastify.d.ts CHANGED
@@ -1,7 +1,33 @@
1
- import { FastifyRequest as FastifyRequestType } from 'fastify'
1
+ import { FastifyRequest as FastifyRequestType, FastifyReply } from 'fastify'
2
2
 
3
3
  type User = Record<string, unknown>
4
4
  type UserData = Record<string, unknown>
5
+ type JwtUserData = UserData
6
+
7
+ type JwtAccessPayload = {
8
+ typ: 'access'
9
+ id: string
10
+ user_data: JwtUserData
11
+ data: JwtUserData
12
+ custom_data?: JwtUserData
13
+ }
14
+
15
+ type JwtRefreshPayload = {
16
+ typ: 'refresh'
17
+ baas_id: string
18
+ }
19
+
20
+ type JwtPayload = JwtAccessPayload | JwtRefreshPayload
21
+
22
+ type JwtAccessUser = JwtAccessPayload & {
23
+ sub: string
24
+ }
25
+
26
+ type JwtRefreshUser = JwtRefreshPayload & {
27
+ sub: string
28
+ }
29
+
30
+ type JwtUser = JwtAccessUser | JwtRefreshUser
5
31
 
6
32
  declare module 'fastify' {
7
33
  interface FastifyInstance {
@@ -9,20 +35,11 @@ declare module 'fastify' {
9
35
  createAccessToken(user: User): string
10
36
  createRefreshToken(user: User): string
11
37
  }
38
+ }
12
39
 
13
- interface FastifyRequest {
14
- user:
15
- | {
16
- id: string
17
- typ: 'refresh'
18
- sub: string
19
- user_data: UserData
20
- user: User
21
- }
22
- | {
23
- typ: 'access'
24
- user_data: UserData
25
- id: string
26
- }
40
+ declare module '@fastify/jwt' {
41
+ interface FastifyJWT {
42
+ payload: JwtPayload
43
+ user: JwtUser
27
44
  }
28
45
  }
@@ -1,5 +1,6 @@
1
1
  import { ObjectId } from 'bson'
2
2
  import { ChangeStream, Document } from 'mongodb';
3
+ import type { FastifyRequest } from 'fastify'
3
4
  import { services } from '../../services'
4
5
  import { StateManager } from '../../state'
5
6
  import { GenerateContext } from '../../utils/context'
@@ -7,7 +8,31 @@ import { Base64Function, FunctionCallBase64Dto, FunctionCallDto } from './dtos'
7
8
  import { FunctionController } from './interface'
8
9
  import { executeQuery } from './utils'
9
10
 
11
+ const normalizeUser = (payload: Record<string, any> | undefined) => {
12
+ if (!payload) return undefined
13
+ const nestedUser =
14
+ payload.data ?? payload.user_data ?? payload.custom_data ?? payload
15
+ const flattened =
16
+ typeof nestedUser === 'object' && nestedUser !== null ? nestedUser : {}
17
+
18
+ return {
19
+ ...payload,
20
+ ...flattened,
21
+ custom_data: payload.custom_data ?? flattened,
22
+ user_data: payload.user_data ?? flattened,
23
+ data: payload.data ?? flattened
24
+ }
25
+ }
26
+
27
+ const getRequestUser = (req: FastifyRequest) => {
28
+ const candidate = req.user as Record<string, any> | undefined
29
+ return normalizeUser(candidate)
30
+ }
10
31
 
32
+ const logFunctionCall = (method: string, user: Record<string, any> | undefined, args: unknown[]) => {
33
+ if (process.env.DEBUG_FUNCTIONS !== 'true') return
34
+ console.log('[functions-debug]', method, user ? { id: user.id, role: user.role, email: user.email } : 'no-user', args)
35
+ }
11
36
 
12
37
  /**
13
38
  * > Creates a pre handler for every query
@@ -24,7 +49,10 @@ export const functionsController: FunctionController = async (
24
49
  const streams = {} as Record<string, ChangeStream<Document, Document>>
25
50
 
26
51
  app.post<{ Body: FunctionCallDto }>('/call', async (req, res) => {
27
- const { user } = req
52
+ const user = getRequestUser(req)
53
+ if (!user || user.typ !== 'access') {
54
+ throw new Error('Access token required')
55
+ }
28
56
  const { name: method, arguments: args } = req.body
29
57
 
30
58
  if ('service' in req.body) {
@@ -39,6 +67,7 @@ export const functionsController: FunctionController = async (
39
67
  .db(database)
40
68
  .collection(collection)[method]
41
69
 
70
+ logFunctionCall(`service:${req.body.service}:${method}`, user, args)
42
71
  const operatorsByType = await executeQuery({
43
72
  currentMethod,
44
73
  query,
@@ -61,6 +90,7 @@ export const functionsController: FunctionController = async (
61
90
  throw new Error(`Function "${req.body.name}" is private`)
62
91
  }
63
92
 
93
+ logFunctionCall(`function:${method}`, user, args)
64
94
  const result = await GenerateContext({
65
95
  args: req.body.arguments,
66
96
  app,
@@ -76,7 +106,11 @@ export const functionsController: FunctionController = async (
76
106
  app.get<{
77
107
  Querystring: FunctionCallBase64Dto
78
108
  }>('/call', async (req, res) => {
79
- const { query, user } = req
109
+ const { query } = req
110
+ const user = getRequestUser(req)
111
+ if (!user || user.typ !== 'access') {
112
+ throw new Error('Access token required')
113
+ }
80
114
  const { baas_request, stitch_request } = query
81
115
 
82
116
  const config: Base64Function = JSON.parse(
@@ -5,11 +5,20 @@ import { Rules, RulesConfig } from './interface'
5
5
 
6
6
  export const loadRules = async (rootDir = process.cwd()): Promise<Rules> => {
7
7
  const rulesRoot = path.join(rootDir, 'data_sources', 'mongodb-atlas')
8
- const files = fs.readdirSync(rulesRoot, { recursive: true }) as string[]
8
+ const recursivelyCollectFiles = (dir: string): string[] => {
9
+ return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
10
+ const fullPath = path.join(dir, entry.name)
11
+ if (entry.isDirectory()) {
12
+ return recursivelyCollectFiles(fullPath)
13
+ }
14
+ return entry.isFile() ? [fullPath] : []
15
+ })
16
+ }
17
+ const files = recursivelyCollectFiles(rulesRoot)
9
18
  const rulesFiles = files.filter((x) => (x as string).endsWith('rules.json'))
10
19
 
11
20
  const rulesByCollection = rulesFiles.reduce((acc, rulesFile) => {
12
- const filePath = path.join(rulesRoot, rulesFile)
21
+ const filePath = rulesFile
13
22
  const collectionRules = readJsonContent(filePath) as RulesConfig
14
23
  acc[collectionRules.collection] = collectionRules
15
24
 
@@ -7,6 +7,41 @@ import { readJsonContent } from '../../utils'
7
7
  import { GenerateContext } from '../../utils/context'
8
8
  import { HandlerParams, Trigger, Triggers } from './interface'
9
9
 
10
+ const registerOnClose = (
11
+ app: HandlerParams['app'],
12
+ handler: () => Promise<void> | void,
13
+ label: string
14
+ ) => {
15
+ if (app.server) {
16
+ app.server.once('close', () => {
17
+ Promise.resolve(handler()).catch((error) => {
18
+ console.error(`${label} close error`, error)
19
+ })
20
+ })
21
+ return
22
+ }
23
+
24
+ try {
25
+ app.addHook('onClose', async () => {
26
+ try {
27
+ await handler()
28
+ } catch (error) {
29
+ console.error(`${label} close error`, error)
30
+ }
31
+ })
32
+ } catch (error) {
33
+ console.error(`${label} hook registration error`, error)
34
+ }
35
+ }
36
+
37
+ const shouldIgnoreStreamError = (error: unknown) => {
38
+ const err = error as { name?: string; message?: string }
39
+ if (err?.name === 'MongoClientClosedError') return true
40
+ if (err?.message?.includes('client was closed')) return true
41
+ if (err?.message?.includes('Client is closed')) return true
42
+ return false
43
+ }
44
+
10
45
  /**
11
46
  * Loads trigger files from the specified directory and returns them as an array of objects.
12
47
  * Each object contains the file name and the parsed JSON content.
@@ -54,7 +89,7 @@ const handleCronTrigger = async ({
54
89
  services,
55
90
  app
56
91
  }: HandlerParams) => {
57
- cron.schedule(config.schedule, async () => {
92
+ const task = cron.schedule(config.schedule, async () => {
58
93
  await GenerateContext({
59
94
  args: [],
60
95
  app,
@@ -65,6 +100,7 @@ const handleCronTrigger = async ({
65
100
  services
66
101
  })
67
102
  })
103
+ registerOnClose(app, () => task.stop(), 'Scheduled trigger')
68
104
  }
69
105
 
70
106
  const handleAuthenticationTrigger = async ({
@@ -88,6 +124,10 @@ const handleAuthenticationTrigger = async ({
88
124
  .watch(pipeline, {
89
125
  fullDocument: 'whenAvailable'
90
126
  })
127
+ changeStream.on('error', (error) => {
128
+ if (shouldIgnoreStreamError(error)) return
129
+ console.error('Authentication trigger change stream error', error)
130
+ })
91
131
  changeStream.on('change', async function (change) {
92
132
  const document = change['fullDocument' as keyof typeof change] as Record<
93
133
  string,
@@ -120,6 +160,13 @@ const handleAuthenticationTrigger = async ({
120
160
  })
121
161
  }
122
162
  })
163
+ registerOnClose(
164
+ app,
165
+ async () => {
166
+ await changeStream.close()
167
+ },
168
+ 'Authentication trigger'
169
+ )
123
170
  }
124
171
 
125
172
  /**
@@ -175,6 +222,10 @@ const handleDataBaseTrigger = async ({
175
222
  ? 'whenAvailable'
176
223
  : undefined
177
224
  })
225
+ changeStream.on('error', (error) => {
226
+ if (shouldIgnoreStreamError(error)) return
227
+ console.error('Database trigger change stream error', error)
228
+ })
178
229
  changeStream.on('change', async function ({ clusterTime, ...change }) {
179
230
  await GenerateContext({
180
231
  args: [change],
@@ -186,7 +237,13 @@ const handleDataBaseTrigger = async ({
186
237
  services
187
238
  })
188
239
  })
189
- // TODO -> gestire close dello stream
240
+ registerOnClose(
241
+ app,
242
+ async () => {
243
+ await changeStream.close()
244
+ },
245
+ 'Database trigger'
246
+ )
190
247
  }
191
248
 
192
249
  export const TRIGGER_HANDLERS = {
package/src/index.ts CHANGED
@@ -14,12 +14,22 @@ import { exposeRoutes } from './utils/initializer/exposeRoutes'
14
14
  import { registerPlugins } from './utils/initializer/registerPlugins'
15
15
  export * from './model'
16
16
 
17
+
18
+ export type ALLOWED_METHODS = "GET" | "POST" | "PUT" | "DELETE"
19
+
20
+ export type CorsConfig = {
21
+ origin: string
22
+ methods: ALLOWED_METHODS[]
23
+ }
24
+
17
25
  export type InitializeConfig = {
18
26
  projectId: string
19
27
  mongodbUrl?: string
20
28
  jwtSecret?: string
21
29
  port?: number
22
30
  host?: string
31
+ corsConfig?: CorsConfig
32
+ basePath?: string
23
33
  }
24
34
 
25
35
  /**
@@ -35,25 +45,27 @@ export async function initialize({
35
45
  host = DEFAULT_CONFIG.HOST,
36
46
  jwtSecret = DEFAULT_CONFIG.JWT_SECRET,
37
47
  port = DEFAULT_CONFIG.PORT,
38
- mongodbUrl = DEFAULT_CONFIG.MONGODB_URL
48
+ mongodbUrl = DEFAULT_CONFIG.MONGODB_URL,
49
+ corsConfig = DEFAULT_CONFIG.CORS_OPTIONS,
50
+ basePath
39
51
  }: InitializeConfig) {
52
+ const resolvedBasePath = basePath ?? require.main?.path ?? process.cwd()
40
53
  const fastify = Fastify({
41
54
  logger: !!DEFAULT_CONFIG.ENABLE_LOGGER
42
55
  })
43
56
 
44
- const basePath = require.main?.path
45
- console.log("BASE PATH", basePath)
57
+ console.log("BASE PATH", resolvedBasePath)
46
58
 
47
59
  console.log("CURRENT PORT", port)
48
60
  console.log("CURRENT HOST", host)
49
61
 
50
- const functionsList = await loadFunctions(basePath)
62
+ const functionsList = await loadFunctions(resolvedBasePath)
51
63
  console.log("Functions LOADED")
52
- const triggersList = await loadTriggers(basePath)
64
+ const triggersList = await loadTriggers(resolvedBasePath)
53
65
  console.log("Triggers LOADED")
54
- const endpointsList = await loadEndpoints(basePath)
66
+ const endpointsList = await loadEndpoints(resolvedBasePath)
55
67
  console.log("Endpoints LOADED")
56
- const rulesList = await loadRules(basePath)
68
+ const rulesList = await loadRules(resolvedBasePath)
57
69
  console.log("Rules LOADED")
58
70
 
59
71
  const stateConfig = {
@@ -91,7 +103,8 @@ export async function initialize({
91
103
  register: fastify.register,
92
104
  mongodbUrl,
93
105
  jwtSecret,
94
- functionsList
106
+ functionsList,
107
+ corsConfig
95
108
  })
96
109
 
97
110
  console.log('Plugins registration COMPLETED')