minimonolith 0.18.0 → 0.19.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.
- package/.env.example +2 -2
- package/README.md +35 -7
- package/package.json +1 -1
- package/src/api/apiHandler.js +3 -1
- package/src/api/createAPI.js +4 -3
- package/src/crasher/crasher.js +1 -0
- package/src/crasher/index.js +3 -0
- package/src/database/databaseService.js +7 -4
- package/src/development/serverHandler.js +3 -1
- package/src/health/healthService.js +3 -1
- package/src/logger/index.js +3 -0
- package/src/logger/logger.js +5 -0
- package/src/model/loadModels.js +7 -5
- package/src/service/methodHandler.js +5 -4
- package/src/service/registerMethod.js +7 -4
- package/src/service/registerService.js +8 -6
- package/src/service/validationHandler.js +0 -16
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ This file validates the `get:id` method's input, ensuring that the provided `id`
|
|
|
106
106
|
// todo/get/valid.js
|
|
107
107
|
import { z, DB_VALIDATION } from 'minimonolith';
|
|
108
108
|
|
|
109
|
-
export default (MODELS
|
|
109
|
+
export default ({ MODELS }) => ({
|
|
110
110
|
id: z.string()
|
|
111
111
|
.superRefine(DB_VALIDATION.isSafeInt('id'))
|
|
112
112
|
.transform(id => parseInt(id))
|
|
@@ -114,6 +114,29 @@ export default (MODELS, ROUTE_CODE) => ({
|
|
|
114
114
|
})
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
## App Environments
|
|
118
|
+
|
|
119
|
+
There are 4 possible environments: DEV=TRUE & PROD=FALSE, DEV=TRUE & PROD=TRUE, DEV=FALSE & PROD=TRUE, DEV=FALSE & PROD=TRUE
|
|
120
|
+
1. DEV=TRUE & PROD=FALSE: This is the standard DEV environment
|
|
121
|
+
2. DEV=FALSE & PROD=FALSE: This is the standard QA environment
|
|
122
|
+
3. DEV=FALSE & PROD=TRUE: This is the stnadard PROD environment
|
|
123
|
+
4. DEV=TRUE & PROD=TRUE: This allows to test the behavior of PROD within the "new concept" of DEV environment
|
|
124
|
+
|
|
125
|
+
To better understand their relevance:
|
|
126
|
+
1. The "new concept" DEV environments (DEV=TRUE) aim to make the api crash if an "important" error happens
|
|
127
|
+
1. Its current only difference is it makes it crash on error at service registration phase
|
|
128
|
+
2. The "new concept" QA environments (PROD=FALSE) aim at logging data about the system which on production environments would be forbiden personal information
|
|
129
|
+
1. This is relevant because replication of QA activities (even security QA activities) depend heavily on this
|
|
130
|
+
|
|
131
|
+
The current App environment is determined on the values of MM_API_DEV ENV [TRUE/FALSE] and MM_API_PROD_ENV [TRUE/FALSE]:
|
|
132
|
+
|
|
133
|
+
```makefile
|
|
134
|
+
# .env standard dev environment
|
|
135
|
+
MM_API_DEV_ENV=TRUE
|
|
136
|
+
MM_API_PROD_ENV=FALSE
|
|
137
|
+
[...]
|
|
138
|
+
```
|
|
139
|
+
|
|
117
140
|
## Database Authentication
|
|
118
141
|
|
|
119
142
|
To set up authentication for the database, you need to provide the necessary environment variables in a `.env` file. Depending on the database dialect the required variables will differ.
|
|
@@ -121,27 +144,32 @@ To set up authentication for the database, you need to provide the necessary env
|
|
|
121
144
|
For MySQL:
|
|
122
145
|
|
|
123
146
|
```makefile
|
|
147
|
+
MM_API_DEV_ENV=TRUE
|
|
148
|
+
MM_API_PROD_ENV=FALSE
|
|
124
149
|
MM_API_DB_DIALECT=mysql
|
|
125
150
|
MM_API_DB_HOST=<your_database_endpoint>
|
|
126
151
|
MM_API_DB_PORT=<your_database_port>
|
|
127
152
|
MM_API_DB_DATABASE=<your_database_name>
|
|
128
153
|
MM_API_DB_USERNAME=<your_database_username>
|
|
129
154
|
MM_API_DB_PASSWORD=<your_database_password>
|
|
130
|
-
MM_API_LOCAL_ENV=true
|
|
131
|
-
MM_API_PROD_ENV=false
|
|
132
155
|
```
|
|
133
156
|
|
|
134
157
|
For SQLite:
|
|
135
158
|
|
|
136
159
|
```makefile
|
|
160
|
+
MM_API_DEV_ENV=TRUE
|
|
161
|
+
MM_API_PROD_ENV=FALSE
|
|
137
162
|
MM_API_DB_DIALECT=sqlite
|
|
138
163
|
MM_API_DB_STORAGE=:memory: # For in-memory SQLite database
|
|
139
164
|
# Or
|
|
140
165
|
MM_API_DB_STORAGE=path/to/your/sqlite/file.db # For file-based SQLite database
|
|
141
166
|
MM_API_DB_DATABASE=<your_database_name>
|
|
142
|
-
MM_API_LOCAL_ENV=true
|
|
143
|
-
MM_API_PROD_ENV=false
|
|
144
167
|
```
|
|
145
168
|
|
|
146
|
-
Make sure to replace the placeholders with your actual database credentials.
|
|
147
|
-
|
|
169
|
+
Make sure to replace the placeholders with your actual database credentials.
|
|
170
|
+
- `MM_API_DEV_ENV=TRUE` allows Sequelize to alter table structure automatically when working locally
|
|
171
|
+
- `MM_API_PROD_ENV=FALSE` allows logging of DB credentials for debugging purposes in non-production environments
|
|
172
|
+
- We consider high quality logging important for app performance and evolution
|
|
173
|
+
- However we recommend automatic DB credentials updates (daily) High quality logging does not mean
|
|
174
|
+
giving away your infraestructure to hackers
|
|
175
|
+
- At the risk of stating the obvious do not store personal information at the QA database
|
package/package.json
CHANGED
package/src/api/apiHandler.js
CHANGED
package/src/api/createAPI.js
CHANGED
|
@@ -4,6 +4,7 @@ import { registerHealthService } from '../health/index.js';
|
|
|
4
4
|
import { registerDatabaseService } from '../database/index.js';
|
|
5
5
|
import { loadAndSyncModels } from '../model/index.js';
|
|
6
6
|
import { registerService } from '../service/index.js';
|
|
7
|
+
import { logger } from '../logger/index.js';
|
|
7
8
|
import { apiHandler } from './apiHandler.js';
|
|
8
9
|
|
|
9
10
|
const ROUTE_CODE = 'CREATE_API';
|
|
@@ -16,7 +17,7 @@ const addCORS = API => {
|
|
|
16
17
|
res.header('Access-Control-Allow-Methods', 'OPTIONS, POST, GET, PUT, PATCH, DELETE');
|
|
17
18
|
res.header('Access-Control-Allow-Headers', '*');
|
|
18
19
|
res.header('Access-Control-Allow-Credentials', true);
|
|
19
|
-
|
|
20
|
+
//logger.log('OPTIONS_SUCCESS');
|
|
20
21
|
res.status(200).send({})
|
|
21
22
|
});
|
|
22
23
|
};
|
|
@@ -35,9 +36,9 @@ const createAPI = () => {
|
|
|
35
36
|
|
|
36
37
|
API.handler = async () => {
|
|
37
38
|
if (API.ORM) API.MODELS = await loadAndSyncModels(API);
|
|
38
|
-
else
|
|
39
|
+
else logger.log({ ROUTE_CODE, INFO: 'NO_DATABASE_REGISTERED' });
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
logger.log({ ROUTE_CODE, INFO: 'LISTENING' });
|
|
41
42
|
return apiHandler(API);
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ERROR => { if (process.env.MM_API_DEV_ENV==='TRUE') throw ERROR; };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import Sequelize from 'sequelize';
|
|
2
2
|
//import SequelizeDynamo from 'dynamo-sequelize';
|
|
3
3
|
|
|
4
|
+
import { logger } from '../logger/index.js';
|
|
5
|
+
import { crasher } from '../crasher/index.js';
|
|
6
|
+
|
|
4
7
|
const ROUTE_CODE = 'DB_CONNECTION';
|
|
5
8
|
|
|
6
9
|
const establishConnection = async ORM => {
|
|
@@ -9,7 +12,7 @@ const establishConnection = async ORM => {
|
|
|
9
12
|
|
|
10
13
|
while (connectionRetries < MAX_RETRIES) {
|
|
11
14
|
try {
|
|
12
|
-
|
|
15
|
+
logger.log({ ROUTE_CODE, AUTH_INTENT: connectionRetries });
|
|
13
16
|
await ORM.authenticate();
|
|
14
17
|
break;
|
|
15
18
|
|
|
@@ -32,14 +35,14 @@ const registerDatabaseService = async API => {
|
|
|
32
35
|
const SEQUELIZE_OPTIONS = { dialect: MM_API_DB_DIALECT, host: MM_API_DB_HOST,
|
|
33
36
|
port: MM_API_DB_PORT, storage: MM_API_DB_STORAGE, logging: false };
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
logger.qa({ ROUTE_CODE, MM_API_DB_VARS: { MM_API_DB_DIALECT,
|
|
36
39
|
MM_API_DB_USER, MM_API_DB_PASS, MM_API_DB_HOST, MM_API_DB_PORT, MM_API_DB_DB, MM_API_DB_STORAGE }});
|
|
37
40
|
API.ORM = new Sequelize(MM_API_DB_DB, MM_API_DB_USER, MM_API_DB_PASS, SEQUELIZE_OPTIONS);
|
|
38
41
|
establishConnection(API.ORM);
|
|
39
42
|
|
|
40
43
|
} catch (REGISTER_DB_ERROR) {
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
logger.error({ ROUTE_CODE, REGISTER_DB_ERROR });
|
|
45
|
+
crasher(REGISTER_DB_ERROR);
|
|
43
46
|
}
|
|
44
47
|
};
|
|
45
48
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import url from 'url';
|
|
4
4
|
|
|
5
|
+
import { logger } from '../logger/index.js';
|
|
6
|
+
|
|
5
7
|
const loadBody = req => {
|
|
6
8
|
return new Promise((resolve, reject) => {
|
|
7
9
|
let body = '';
|
|
@@ -59,7 +61,7 @@ const getServerHandler = lambdaHandler => async (req, res) => {
|
|
|
59
61
|
res.statusCode = lambdaRes.statusCode;
|
|
60
62
|
res.end(lambdaRes.body);
|
|
61
63
|
|
|
62
|
-
} catch(SERVER_ERROR) {
|
|
64
|
+
} catch(SERVER_ERROR) { logger.log({ SERVER_ERROR }); }
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export { getServerHandler };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { logger } from '../logger/index.js';
|
|
2
|
+
|
|
1
3
|
const healthHandler = () => {
|
|
2
4
|
return "API_RUNNING";
|
|
3
5
|
}
|
|
@@ -5,7 +7,7 @@ const healthHandler = () => {
|
|
|
5
7
|
const registerHealthService = API => {
|
|
6
8
|
API.get('/', async (req, res) => {
|
|
7
9
|
const SERVICE_RESPONSE = healthHandler();
|
|
8
|
-
|
|
10
|
+
logger.log({ SERVICE_RESPONSE });
|
|
9
11
|
return { SERVICE_RESPONSE };
|
|
10
12
|
});
|
|
11
13
|
}
|
package/src/model/loadModels.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DataTypes } from 'sequelize';
|
|
2
2
|
|
|
3
|
+
import { logger } from '../logger/index.js';
|
|
4
|
+
|
|
3
5
|
const ROUTE_CODE = 'LOAD_MODELS';
|
|
4
6
|
|
|
5
7
|
const modelSchemas = {};
|
|
@@ -11,19 +13,19 @@ const registerModel = async (SERVICE_NAME, SERVICE_URL) => {
|
|
|
11
13
|
|
|
12
14
|
const loadAndSyncModels = async API => {
|
|
13
15
|
const MODELS = Object.keys(modelSchemas).reduce((loadedModels, serviceName) => {
|
|
14
|
-
|
|
16
|
+
logger.log({ ROUTE_CODE, LOADING_MODEL: serviceName });
|
|
15
17
|
loadedModels[serviceName] = modelSchemas[serviceName](API.ORM, DataTypes);
|
|
16
18
|
return loadedModels;
|
|
17
19
|
}, {});
|
|
18
20
|
|
|
19
21
|
Object.entries(MODELS).forEach(([serviceName, model]) => {
|
|
20
|
-
|
|
22
|
+
logger.log({ ROUTE_CODE, ASSOCIATING_MODEL: serviceName });
|
|
21
23
|
model.associate(MODELS);
|
|
22
24
|
});
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
await API.ORM.sync({ alter: process.env.
|
|
26
|
-
|
|
26
|
+
logger.log({ ROUTE_CODE, SYNCING_ORM: 'WAITING_FOR_ORM_SYNCING' });
|
|
27
|
+
await API.ORM.sync({ alter: process.env.MM_API_DEV_ENV==='TRUE' ? true : false });
|
|
28
|
+
logger.log({ ROUTE_CODE, SYNCING_ORM: 'DONE_ORM_SYNCING' });
|
|
27
29
|
|
|
28
30
|
return MODELS;
|
|
29
31
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { logger } from '../logger/index.js';
|
|
2
|
+
|
|
1
3
|
const methodHandler = async (res, METHOD, EVENT_CONTEXT) => {
|
|
2
4
|
const { body, ROUTE_CODE } = EVENT_CONTEXT;
|
|
3
5
|
|
|
@@ -6,17 +8,16 @@ const methodHandler = async (res, METHOD, EVENT_CONTEXT) => {
|
|
|
6
8
|
const validation = await METHOD.VALIDATOR(EVENT_CONTEXT).safeParseAsync(body);
|
|
7
9
|
if (validation.success) {
|
|
8
10
|
const METHOD_RESPONSE = await METHOD.handler(EVENT_CONTEXT);
|
|
9
|
-
|
|
10
|
-
console.log({ ROUTE_CODE, METHOD_RESPONSE: METHOD_RESPONSE ? METHOD_RESPONSE.toString(): METHOD_RESPONSE });
|
|
11
|
+
logger.log({ ROUTE_CODE, METHOD_RESPONSE: METHOD_RESPONSE ? METHOD_RESPONSE.toString(): METHOD_RESPONSE });
|
|
11
12
|
res.status(200).json(METHOD_RESPONSE); return;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const VALIDATION_ERROR = validation.error.format();
|
|
15
|
-
|
|
16
|
+
logger.error({ ROUTE_CODE, VALIDATION_ERROR });
|
|
16
17
|
res.status(400).json({ ROUTE_CODE, VALIDATION_ERROR });
|
|
17
18
|
}
|
|
18
19
|
catch (METHOD_ERROR) {
|
|
19
|
-
|
|
20
|
+
logger.error({ ROUTE_CODE, METHOD_ERROR: METHOD_ERROR.stack });
|
|
20
21
|
res.status(500).json({ ROUTE_CODE, METHOD_ERROR: METHOD_ERROR.stack.toString() });
|
|
21
22
|
}
|
|
22
23
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import url from 'url';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
|
|
4
5
|
import { z, optionalZObject } from '../zod/index.js';
|
|
6
|
+
import { logger } from '../logger/index.js';
|
|
7
|
+
import { crasher } from '../crasher/index.js';
|
|
5
8
|
|
|
6
9
|
const registerMethod = async (SERVICE_NAME, SERVICE_URL, METHOD_NAME, METHOD_ROUTE_CODE) => {
|
|
7
10
|
|
|
@@ -9,14 +12,14 @@ const registerMethod = async (SERVICE_NAME, SERVICE_URL, METHOD_NAME, METHOD_ROU
|
|
|
9
12
|
const method = { VALIDATOR: EVENT_CONTEXT => z.undefined() };
|
|
10
13
|
|
|
11
14
|
try {
|
|
12
|
-
|
|
15
|
+
logger.log(' FOUND_METHOD', METHOD_NAME);
|
|
13
16
|
|
|
14
17
|
method.handler = (await import(new URL(`${METHOD_NAME}/index.js`, SERVICE_URL))).default;
|
|
15
18
|
|
|
16
19
|
const VALIDATOR_URL = new URL(`${METHOD_NAME}/valid.js`, SERVICE_URL);
|
|
17
20
|
if (fs.existsSync(url.fileURLToPath(VALIDATOR_URL))) {
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
logger.log(' FOUND_VALIDATOR');
|
|
20
23
|
|
|
21
24
|
const Z_ELEMENTS = (await import(VALIDATOR_URL)).default;
|
|
22
25
|
method.VALIDATOR = EVENT_CONTEXT => optionalZObject(Z_ELEMENTS(EVENT_CONTEXT));
|
|
@@ -25,8 +28,8 @@ const registerMethod = async (SERVICE_NAME, SERVICE_URL, METHOD_NAME, METHOD_ROU
|
|
|
25
28
|
return method;
|
|
26
29
|
|
|
27
30
|
} catch (REGISTER_METHOD_ERROR) {
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
logger.error({ ROUTE_CODE, REGISTER_METHOD_ERROR, STACK_TRASE: REGISTER_METHOD_ERROR.stack });
|
|
32
|
+
crasher(REGISTER_METHOD_ERROR);
|
|
30
33
|
}
|
|
31
34
|
};
|
|
32
35
|
|
|
@@ -3,6 +3,8 @@ import url from 'url'
|
|
|
3
3
|
|
|
4
4
|
import { getProjectRoot } from '../path/index.js';
|
|
5
5
|
import { registerModel } from '../model/index.js';
|
|
6
|
+
import { logger } from '../logger/index.js';
|
|
7
|
+
import { crasher } from '../crasher/index.js';
|
|
6
8
|
import { methodHandler } from './methodHandler.js';
|
|
7
9
|
import { getMethodType } from './getMethodType.js';
|
|
8
10
|
import { getMethodRouteCode } from './getMethodRouteCode.js';
|
|
@@ -13,23 +15,23 @@ const ROUTE_CODE = 'REGISTER_SERVICE';
|
|
|
13
15
|
|
|
14
16
|
const registerService = async (API, SERVICE_NAME, SRC_FOLDER, MODULE_FOLDER) => {
|
|
15
17
|
try {
|
|
16
|
-
|
|
18
|
+
logger.log(ROUTE_CODE, SERVICE_NAME);
|
|
17
19
|
const PROJECT_ROOT_PATH = getProjectRoot(import.meta.url, MODULE_FOLDER)+'/';
|
|
18
20
|
const PROJECT_RELATIVE_SERVICE_PATH = './' + SRC_FOLDER + `${SERVICE_NAME}/`;
|
|
19
21
|
const SERVICE_URL = new URL(PROJECT_RELATIVE_SERVICE_PATH, PROJECT_ROOT_PATH);
|
|
20
22
|
|
|
21
23
|
const SERVICE_MODULE = await import(`${SERVICE_URL}index.js`);
|
|
22
24
|
if (fs.existsSync(url.fileURLToPath(`${SERVICE_URL}model.js`))) {
|
|
23
|
-
|
|
25
|
+
logger.log(' FOUND_MODEL');
|
|
24
26
|
registerModel(SERVICE_NAME, SERVICE_URL);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
logger.log('METHODS_TO_BE_REGISTERED', SERVICE_MODULE.default);
|
|
28
30
|
|
|
29
31
|
API.SERVICES[SERVICE_NAME] = {}; const SERVICE_METHODS = [];
|
|
30
32
|
for (const METHOD_CODE of SERVICE_MODULE.default) {
|
|
31
33
|
const { METHOD_NAME, METHOD_ROUTE } = parseMethodCode(SERVICE_NAME, METHOD_CODE);
|
|
32
|
-
|
|
34
|
+
logger.log('REGISTERING_ROUTE', METHOD_ROUTE);
|
|
33
35
|
|
|
34
36
|
const METHOD_ROUTE_CODE = getMethodRouteCode(SERVICE_NAME, METHOD_NAME);
|
|
35
37
|
|
|
@@ -55,13 +57,13 @@ const registerService = async (API, SERVICE_NAME, SRC_FOLDER, MODULE_FOLDER) =>
|
|
|
55
57
|
});
|
|
56
58
|
};
|
|
57
59
|
} catch (REGISTER_SERVICE_ERROR) {
|
|
58
|
-
|
|
60
|
+
logger.error({
|
|
59
61
|
ROUTE_CODE,
|
|
60
62
|
SERVICE_NAME,
|
|
61
63
|
REGISTER_SERVICE_ERROR,
|
|
62
64
|
STACK_TRASE: REGISTER_SERVICE_ERROR.stack,
|
|
63
65
|
});
|
|
64
|
-
|
|
66
|
+
crasher(REGISTER_SERVICE_ERROR);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const validationHandler = async (res, METHOD, EVENT_CONTEXT) => {
|
|
2
|
-
const { body, ROUTE_CODE } = EVENT_CONTEXT;
|
|
3
|
-
|
|
4
|
-
const validation = await METHOD.VALIDATOR(EVENT_CONTEXT).safeParseAsync(body);
|
|
5
|
-
if (validation.success) {
|
|
6
|
-
const METHOD_RESPONSE = await METHOD.handler(EVENT_CONTEXT);
|
|
7
|
-
console.log({ ROUTE_CODE, METHOD_RESPONSE: METHOD_RESPONSE ? METHOD_RESPONSE.toString(): METHOD_RESPONSE });
|
|
8
|
-
res.status(200).json(METHOD_RESPONSE); return;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const VALIDATION_ERROR = validation.error.format();
|
|
12
|
-
console.error({ ROUTE_CODE, VALIDATION_ERROR });
|
|
13
|
-
res.status(400).json({ ROUTE_CODE, VALIDATION_ERROR });
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export { validationHandler };
|