@gnar-engine/cli 1.0.4 → 1.0.5
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/bootstrap/deploy.localdev.yml +30 -3
- package/bootstrap/secrets.localdev.yml +15 -4
- package/bootstrap/services/control/src/config.js +4 -0
- package/bootstrap/services/page/Dockerfile +23 -0
- package/bootstrap/services/page/package.json +16 -0
- package/bootstrap/services/page/src/app.js +50 -0
- package/bootstrap/services/page/src/commands/block.handler.js +94 -0
- package/bootstrap/services/page/src/commands/page.handler.js +167 -0
- package/bootstrap/services/page/src/config.js +62 -0
- package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
- package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
- package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
- package/bootstrap/services/page/src/policies/block.policy.js +50 -0
- package/bootstrap/services/page/src/policies/page.policy.js +49 -0
- package/bootstrap/services/page/src/schema/page.schema.js +139 -0
- package/bootstrap/services/page/src/services/block.service.js +83 -0
- package/bootstrap/services/page/src/services/page.service.js +83 -0
- package/bootstrap/services/portal/Dockerfile +20 -0
- package/bootstrap/services/portal/README.md +73 -0
- package/bootstrap/services/portal/index.html +13 -0
- package/bootstrap/services/portal/nginx.conf +5 -0
- package/bootstrap/services/portal/package.json +33 -0
- package/bootstrap/services/portal/public/vite.svg +1 -0
- package/bootstrap/services/portal/react-router.config.js +7 -0
- package/bootstrap/services/portal/src/App.jsx +16 -0
- package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
- package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
- package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
- package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
- package/bootstrap/services/portal/src/assets/react.svg +1 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
- package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.jsx +81 -0
- package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.less +0 -0
- package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
- package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
- package/bootstrap/services/portal/src/css/style.css +711 -0
- package/bootstrap/services/portal/src/data/pages.data.js +10 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.less +102 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
- package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
- package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
- package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
- package/bootstrap/services/portal/src/main.jsx +51 -0
- package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
- package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
- package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
- package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
- package/bootstrap/services/portal/src/services/block.js +28 -0
- package/bootstrap/services/portal/src/services/client.js +67 -0
- package/bootstrap/services/portal/src/services/gravatar.js +14 -0
- package/bootstrap/services/portal/src/services/page.js +28 -0
- package/bootstrap/services/portal/src/services/storage.js +62 -0
- package/bootstrap/services/portal/src/services/user.js +41 -0
- package/bootstrap/services/portal/src/slices/authSlice.js +101 -0
- package/bootstrap/services/portal/src/store/configureStore.js +10 -0
- package/bootstrap/services/portal/src/style/cards.less +57 -0
- package/bootstrap/services/portal/src/style/global.less +204 -0
- package/bootstrap/services/portal/src/style/icons.less +21 -0
- package/bootstrap/services/portal/src/style/inputs.less +52 -0
- package/bootstrap/services/portal/src/style/main.less +28 -0
- package/bootstrap/services/portal/src/utils/utils.js +9 -0
- package/bootstrap/services/portal/vite.config.js +12 -0
- package/bootstrap/services/user/src/app.js +6 -1
- package/bootstrap/services/user/src/commands/user.handler.js +0 -3
- package/bootstrap/services/user/src/config.js +5 -1
- package/bootstrap/services/user/src/policies/user.policy.js +3 -1
- package/bootstrap/services/user/src/tests/commands/user.test.js +22 -0
- package/install-from-clone.sh +30 -0
- package/package.json +1 -1
- package/src/cli.js +8 -0
- package/src/dev/commands.js +10 -2
- package/src/dev/dev.service.js +147 -60
- package/src/provisioner/Dockerfile +27 -0
- package/src/provisioner/package.json +19 -0
- package/src/provisioner/src/app.js +56 -0
- package/src/provisioner/src/services/mongodb.js +58 -0
- package/src/provisioner/src/services/mysql.js +51 -0
- package/src/provisioner/src/services/secrets.js +84 -0
- package/src/scaffolder/commands.js +1 -1
- package/src/scaffolder/scaffolder.handler.js +40 -15
- package/templates/service/src/app.js.hbs +12 -1
- package/templates/service/src/commands/{{serviceName}}.handler.js.hbs +1 -1
- package/templates/service/src/mongodb.config.js.hbs +5 -1
- package/templates/service/src/mysql.config.js.hbs +4 -0
- package/bootstrap/services/user/src/tests/user.test.js +0 -126
|
@@ -13,7 +13,7 @@ config:
|
|
|
13
13
|
min_tasks: 1
|
|
14
14
|
max_tasks: 1
|
|
15
15
|
depends_on:
|
|
16
|
-
-
|
|
16
|
+
- db-mysql
|
|
17
17
|
command:
|
|
18
18
|
- npm
|
|
19
19
|
- run
|
|
@@ -28,11 +28,38 @@ config:
|
|
|
28
28
|
min_tasks: 1
|
|
29
29
|
max_tasks: 1
|
|
30
30
|
depends_on:
|
|
31
|
-
-
|
|
31
|
+
- db-mysql
|
|
32
32
|
command:
|
|
33
33
|
- npm
|
|
34
34
|
- run
|
|
35
35
|
- start:dev
|
|
36
36
|
ports:
|
|
37
37
|
- '4002:3000'
|
|
38
|
-
|
|
38
|
+
- name: portal
|
|
39
|
+
listener_rules:
|
|
40
|
+
paths:
|
|
41
|
+
- /portal
|
|
42
|
+
min_tasks: 1
|
|
43
|
+
max_tasks: 1
|
|
44
|
+
command:
|
|
45
|
+
- npm
|
|
46
|
+
- run
|
|
47
|
+
- dev
|
|
48
|
+
- '-- --host'
|
|
49
|
+
ports:
|
|
50
|
+
- '4003:5173'
|
|
51
|
+
- name: page
|
|
52
|
+
listener_rules:
|
|
53
|
+
paths:
|
|
54
|
+
- /pages
|
|
55
|
+
- /blocks
|
|
56
|
+
min_tasks: 1
|
|
57
|
+
max_tasks: 1
|
|
58
|
+
command:
|
|
59
|
+
- npm
|
|
60
|
+
- run
|
|
61
|
+
- start:dev
|
|
62
|
+
ports:
|
|
63
|
+
- '4004:3000'
|
|
64
|
+
depends_on:
|
|
65
|
+
- db-mongo
|
|
@@ -1,22 +1,33 @@
|
|
|
1
|
+
provision:
|
|
2
|
+
MYSQL_ROOT_PASSWORD: GExtMVydbpIihS5f
|
|
3
|
+
MONGO_ROOT_PASSWORD: E7lVrilnp07gIxcX
|
|
1
4
|
global:
|
|
2
5
|
RABBITMQ_URL: amqp://gnar_engine_rabbit_user:gn4rlyRaB81Tw4sh@rabbitmq:5672
|
|
3
6
|
RABBITMQ_USER: gnar_engine_rabbit_user
|
|
4
7
|
RABBITMQ_PASS: gn4rlyRaB81Tw4sh
|
|
8
|
+
UPLOADS_URL: https://gnar-engine-test.s3.eu-west-2.amazonaws.com
|
|
9
|
+
S3_BUCKET: gnar-engine-test
|
|
10
|
+
AWS_REGION: eu-west-2
|
|
11
|
+
AWS_ACCESS_KEY_ID:
|
|
12
|
+
AWS_SECRET_ACCESS_KEY:
|
|
5
13
|
services:
|
|
6
14
|
control:
|
|
7
|
-
MYSQL_HOST:
|
|
15
|
+
MYSQL_HOST: db-mysql
|
|
8
16
|
MYSQL_DATABASE: ge_control_db
|
|
9
17
|
MYSQL_USER: ge_control_db_user
|
|
10
18
|
MYSQL_PASSWORD: bL6I1ABJFDPO3JAR
|
|
11
|
-
MYSQL_RANDOM_ROOT_PASSWORD: GExtMVydbpIihS5f
|
|
12
19
|
user:
|
|
13
|
-
MYSQL_HOST:
|
|
20
|
+
MYSQL_HOST: db-mysql
|
|
14
21
|
MYSQL_DATABASE: ge_user_db
|
|
15
22
|
MYSQL_USER: ge_user_db_user
|
|
16
23
|
MYSQL_PASSWORD: 7cm0crGDTaJy9Sa2
|
|
17
|
-
MYSQL_RANDOM_ROOT_PASSWORD: APJW3b7teaRHTvcv
|
|
18
24
|
ROOT_ADMIN_EMAIL: adam@gnar.co.uk
|
|
19
25
|
ROOT_ADMIN_USERNAME: engineadmin
|
|
20
26
|
ROOT_ADMIN_PASSWORD: MOF4TWiFh4TXOHcL
|
|
21
27
|
ROOT_ADMIN_API_KEY: JPpmhQN0bs8f5x2jN5ypCU6Ww6VETrW2
|
|
28
|
+
page:
|
|
29
|
+
MONGO_HOST: db-mongo
|
|
30
|
+
MONGO_DATABASE: page_db
|
|
31
|
+
MONGO_USER: page_user
|
|
32
|
+
MONGO_PASSWORD: Q2atGxh21VqNIrZO
|
|
22
33
|
|
|
@@ -6,6 +6,10 @@ export const config = {
|
|
|
6
6
|
// service name
|
|
7
7
|
serviceName: 'controlService',
|
|
8
8
|
|
|
9
|
+
// environment
|
|
10
|
+
environment: process.env.CONTROL_NODE_ENV || 'dev',
|
|
11
|
+
runTests: process.env.CONTROL_RUN_TESTS || false,
|
|
12
|
+
|
|
9
13
|
// microservice | modular-monolith
|
|
10
14
|
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
|
11
15
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Dockerfile for Page Service
|
|
2
|
+
FROM node:20-alpine
|
|
3
|
+
|
|
4
|
+
# Set the working directory
|
|
5
|
+
WORKDIR /usr/gnar_engine/app
|
|
6
|
+
|
|
7
|
+
# Define a global env var
|
|
8
|
+
ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
|
|
9
|
+
|
|
10
|
+
# Copy package.json and package-lock.json
|
|
11
|
+
COPY ./services/page/package*.json ./
|
|
12
|
+
|
|
13
|
+
# Install nodemon
|
|
14
|
+
RUN npm install -g nodemon
|
|
15
|
+
|
|
16
|
+
# Install app dependencies
|
|
17
|
+
RUN npm install
|
|
18
|
+
|
|
19
|
+
# Expose the port the service will run on
|
|
20
|
+
EXPOSE 3000
|
|
21
|
+
|
|
22
|
+
# Start the application
|
|
23
|
+
CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "page-service",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Page microservice for Gnar Engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node ./src/app.js",
|
|
8
|
+
"start:dev": "nodemon --watch ./src ./src/app.js",
|
|
9
|
+
"test": "jest --watchAll --verbose"
|
|
10
|
+
},
|
|
11
|
+
"author": "Gnar Software Ltd.",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@gnar-engine/core": "^1.0.1"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { message, http, logger, db, registerService, webSockets, test } from '@gnar-engine/core';
|
|
2
|
+
import { config } from './config.js';
|
|
3
|
+
import { messageHandlers } from './controllers/message.controller.js';
|
|
4
|
+
import { httpController as pagePlatformHttpController } from './controllers/page.http.controller.js';
|
|
5
|
+
import { httpController as blockPlatformHttpController } from './controllers/block.http.controller.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Initialise service
|
|
9
|
+
*/
|
|
10
|
+
export const initService = async () => {
|
|
11
|
+
|
|
12
|
+
// Run seeders
|
|
13
|
+
db.seeders.runSeeders({config});
|
|
14
|
+
|
|
15
|
+
// Import command handlers after the command bus is initialised
|
|
16
|
+
await import('./commands/page.handler.js');
|
|
17
|
+
await import('./commands/block.handler.js');
|
|
18
|
+
|
|
19
|
+
// Initialise and register message handlers
|
|
20
|
+
await message.init({
|
|
21
|
+
config: config.message,
|
|
22
|
+
handlers: messageHandlers
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Initialise websocket client & server
|
|
26
|
+
await webSockets.init(config.webSockets, config.serviceName);
|
|
27
|
+
|
|
28
|
+
// Register http routes
|
|
29
|
+
await http.registerRoutes({
|
|
30
|
+
controllers: [
|
|
31
|
+
pagePlatformHttpController,
|
|
32
|
+
blockPlatformHttpController
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Start the HTTP server
|
|
37
|
+
await http.start();
|
|
38
|
+
|
|
39
|
+
// Register service with control service
|
|
40
|
+
await registerService();
|
|
41
|
+
|
|
42
|
+
logger.info('G n a r E n g i n e | Page Service initialised successfully.');
|
|
43
|
+
|
|
44
|
+
// Tests
|
|
45
|
+
if (config.environment === 'test' && config.runTests) {
|
|
46
|
+
test.runCommandTests({config});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
initService();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { commands, logger, error } from '@gnar-engine/core';
|
|
2
|
+
import { block } from '../services/block.service.js';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { validateBlock } from '../schema/page.schema.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get single block
|
|
9
|
+
*/
|
|
10
|
+
commands.register('pageService.getSingleBlock', async ({id}) => {
|
|
11
|
+
if (id) {
|
|
12
|
+
return await block.getById({id: id});
|
|
13
|
+
} else {
|
|
14
|
+
throw new error.badRequest('Block id required');
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get many blocks
|
|
20
|
+
*/
|
|
21
|
+
commands.register('pageService.getManyBlocks', async ({}) => {
|
|
22
|
+
return await block.getAll();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create blocks
|
|
27
|
+
*/
|
|
28
|
+
commands.register('pageService.createBlocks', async ({ blocks }) => {
|
|
29
|
+
const validationErrors = [];
|
|
30
|
+
let createdNewBlocks = [];
|
|
31
|
+
|
|
32
|
+
for (const newData of blocks) {
|
|
33
|
+
const { errors } = validateBlock(newData);
|
|
34
|
+
if (errors?.length) {
|
|
35
|
+
validationErrors.push(errors);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const created = await block.create(newData);
|
|
40
|
+
createdNewBlocks.push(created);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (validationErrors.length) {
|
|
44
|
+
throw new error.badRequest(`Invalid block data: ${validationErrors}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return createdNewBlocks;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Update block
|
|
52
|
+
*/
|
|
53
|
+
commands.register('pageService.updateBlock', async ({id, newBlockData}) => {
|
|
54
|
+
|
|
55
|
+
const validationErrors = [];
|
|
56
|
+
|
|
57
|
+
if (!id) {
|
|
58
|
+
throw new error.badRequest('Block ID required');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const obj = await block.getById({id: id});
|
|
62
|
+
|
|
63
|
+
if (!obj) {
|
|
64
|
+
throw new error.notFound('Block not found');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
delete newBlockData.id;
|
|
68
|
+
|
|
69
|
+
const { errors } = validateBlock(newBlockData);
|
|
70
|
+
|
|
71
|
+
if (errors?.length) {
|
|
72
|
+
validationErrors.push(errors);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (validationErrors.length) {
|
|
76
|
+
throw new error.badRequest(`Invalid block data: ${validationErrors}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return await block.update({
|
|
80
|
+
id: id,
|
|
81
|
+
updatedData: newBlockData
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Delete block
|
|
87
|
+
*/
|
|
88
|
+
commands.register('pageService.deleteBlock', async ({id}) => {
|
|
89
|
+
const obj = await block.getById({id: id});
|
|
90
|
+
if (!obj) {
|
|
91
|
+
throw new error.notFound('Block not found');
|
|
92
|
+
}
|
|
93
|
+
return await block.delete({id: id});
|
|
94
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { commands, logger, error, storage } from '@gnar-engine/core';
|
|
2
|
+
import { page } from '../services/page.service.js';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { validatePage } from '../schema/page.schema.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get single page
|
|
9
|
+
*/
|
|
10
|
+
commands.register('pageService.getSinglePage', async ({id}) => {
|
|
11
|
+
if (id) {
|
|
12
|
+
return await page.getById({id: id});
|
|
13
|
+
} else {
|
|
14
|
+
throw new error.badRequest('Page email or id required');
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get many pages
|
|
20
|
+
*/
|
|
21
|
+
commands.register('pageService.getManyPages', async ({}) => {
|
|
22
|
+
return await page.getAll();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create pages
|
|
27
|
+
*/
|
|
28
|
+
commands.register('pageService.createPages', async ({ pages, requestUser }) => {
|
|
29
|
+
const validationErrors = [];
|
|
30
|
+
let createdNewPages = [];
|
|
31
|
+
|
|
32
|
+
for (const newData of pages) {
|
|
33
|
+
const { errors } = validatePage(newData);
|
|
34
|
+
if (errors?.length) {
|
|
35
|
+
validationErrors.push(errors);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
newData = await commands.execute('processUploadsInData', { data: newData, requestUser });
|
|
40
|
+
|
|
41
|
+
const created = await page.create(newData);
|
|
42
|
+
createdNewPages.push(created);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (validationErrors.length) {
|
|
46
|
+
throw new error.badRequest(`Invalid page data: ${validationErrors}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return createdNewPages;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update page
|
|
54
|
+
*/
|
|
55
|
+
commands.register('pageService.updatePage', async ({id, newPageData, requestUser}) => {
|
|
56
|
+
|
|
57
|
+
const validationErrors = [];
|
|
58
|
+
|
|
59
|
+
if (!id) {
|
|
60
|
+
throw new error.badRequest('Page ID required');
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const obj = await page.getById({id: id});
|
|
65
|
+
|
|
66
|
+
if (!obj) {
|
|
67
|
+
throw new error.notFound('Page not found');
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
delete newPageData.id;
|
|
72
|
+
|
|
73
|
+
const { errors } = validatePage(newPageData);
|
|
74
|
+
|
|
75
|
+
if (errors?.length) {
|
|
76
|
+
validationErrors.push(errors);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (validationErrors.length) {
|
|
80
|
+
throw new error.badRequest(`Invalid page data: ${validationErrors}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
newPageData = await commands.execute('processUploadsInData', { data: newPageData, requestUser });
|
|
84
|
+
|
|
85
|
+
return await page.update({
|
|
86
|
+
id: id,
|
|
87
|
+
updatedData: newPageData
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Delete page
|
|
93
|
+
*/
|
|
94
|
+
commands.register('pageService.deletePage', async ({id}) => {
|
|
95
|
+
const obj = await page.getById({id: id});
|
|
96
|
+
if (!obj) {
|
|
97
|
+
throw new error.notFound('Page not found');
|
|
98
|
+
}
|
|
99
|
+
return await page.delete({id: id});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Find file and image uploads and store them
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} data - The data object to search for files/images
|
|
107
|
+
* @param {Function} uploadFn - Function to handle the actual upload process
|
|
108
|
+
* @returns {Object} - The updated data object with stored file/image references
|
|
109
|
+
*/
|
|
110
|
+
commands.register('pageService.processUploadsInData', async ({ data, requestUser }) => {
|
|
111
|
+
|
|
112
|
+
const uploadFilesRecursive = async (data) => {
|
|
113
|
+
if (Array.isArray(data)) {
|
|
114
|
+
return Promise.all(data.map(item => uploadFilesRecursive(item)));
|
|
115
|
+
} else if (data && typeof data === 'object') {
|
|
116
|
+
const result = { ...data };
|
|
117
|
+
|
|
118
|
+
for (const [key, value] of Object.entries(data)) {
|
|
119
|
+
if (key === 'file' && typeof value === 'string') {
|
|
120
|
+
|
|
121
|
+
logger.info('Processing file upload in page data');
|
|
122
|
+
|
|
123
|
+
// Filename
|
|
124
|
+
const fileName = result.fileName || `upload_${Date.now()}`;
|
|
125
|
+
|
|
126
|
+
// Mime type
|
|
127
|
+
let mimeType = result.mimeType;
|
|
128
|
+
let base64Data = value;
|
|
129
|
+
|
|
130
|
+
const matches = value.match(/^data:(.+);base64,(.+)$/);
|
|
131
|
+
if (matches) {
|
|
132
|
+
mimeType = mimeType || matches[1];
|
|
133
|
+
base64Data = matches[2];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!mimeType) mimeType = 'application/octet-stream';
|
|
137
|
+
|
|
138
|
+
// Upload
|
|
139
|
+
const url = await storage.upload({
|
|
140
|
+
file: Buffer.from(base64Data, 'base64'),
|
|
141
|
+
key: 'public/page-content/' + fileName,
|
|
142
|
+
contentType: mimeType,
|
|
143
|
+
metadata: {
|
|
144
|
+
uploadedAt: new Date().toISOString(),
|
|
145
|
+
uploadedBy: requestUser ? requestUser.id : 'unknown'
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Add url and remove upload keys
|
|
150
|
+
result.url = url;
|
|
151
|
+
delete result.file;
|
|
152
|
+
if (result.fileName) delete result.fileName;
|
|
153
|
+
if (result.mimeType) delete result.mimeType;
|
|
154
|
+
|
|
155
|
+
} else {
|
|
156
|
+
result[key] = await uploadFilesRecursive(value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return data;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return await uploadFilesRecursive(data);
|
|
167
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gnar Engine Service Config
|
|
3
|
+
*/
|
|
4
|
+
export const config = {
|
|
5
|
+
// service name
|
|
6
|
+
serviceName: 'pageService',
|
|
7
|
+
|
|
8
|
+
// microservice | modular-monolith
|
|
9
|
+
architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
|
|
10
|
+
|
|
11
|
+
// web server
|
|
12
|
+
http: {
|
|
13
|
+
allowedOrigins: [],
|
|
14
|
+
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
15
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
16
|
+
rateLimiting: {
|
|
17
|
+
max: 5,
|
|
18
|
+
timeWindow: '1 minute',
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// database
|
|
23
|
+
db: {
|
|
24
|
+
// type: mongodb | mysql
|
|
25
|
+
type: 'mongodb',
|
|
26
|
+
|
|
27
|
+
// MongoDB
|
|
28
|
+
host: process.env.PAGE_MONGO_HOST,
|
|
29
|
+
database: process.env.PAGE_MONGO_DATABASE,
|
|
30
|
+
user: process.env.PAGE_MONGO_USER,
|
|
31
|
+
password: process.env.PAGE_MONGO_PASSWORD,
|
|
32
|
+
port: process.env.PAGE_MONGO_PORT || 27017,
|
|
33
|
+
connectionArgs: {},
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// storage
|
|
37
|
+
storage: {
|
|
38
|
+
// driver: s3
|
|
39
|
+
driver: 's3',
|
|
40
|
+
uploadsUrl: process.env.UPLOADS_URL,
|
|
41
|
+
|
|
42
|
+
// s3
|
|
43
|
+
bucket: process.env.S3_BUCKET,
|
|
44
|
+
region: process.env.AWS_REGION,
|
|
45
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
46
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// message broker
|
|
50
|
+
message: {
|
|
51
|
+
url: process.env.RABBITMQ_URL,
|
|
52
|
+
queueName: 'pageServiceQueue',
|
|
53
|
+
prefetch: 20
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
webSockets: {
|
|
57
|
+
reconnectInterval: 5000,
|
|
58
|
+
maxInitialConnectionAttempts: 5
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
hashNameSpace: '',
|
|
62
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { commands } from '@gnar-engine/core';
|
|
2
|
+
import { authorise } from '../policies/block.policy.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HTTP controller
|
|
6
|
+
*/
|
|
7
|
+
export const httpController = {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get single block
|
|
11
|
+
*/
|
|
12
|
+
getSingle: {
|
|
13
|
+
method: 'GET',
|
|
14
|
+
url: '/blocks/:id',
|
|
15
|
+
preHandler: async (request, reply) => authorise.getSingle(request, reply),
|
|
16
|
+
handler: async (request, reply) => {
|
|
17
|
+
const params = {
|
|
18
|
+
id: request.params.id
|
|
19
|
+
};
|
|
20
|
+
const result = await commands.execute('getSingleBlock', params);
|
|
21
|
+
reply.code(200).send({ block: result });
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get multiple blocks
|
|
27
|
+
*/
|
|
28
|
+
getMany: {
|
|
29
|
+
method: 'GET',
|
|
30
|
+
url: '/blocks/',
|
|
31
|
+
preHandler: async (request, reply) => authorise.getMany(request, reply),
|
|
32
|
+
handler: async (request, reply) => {
|
|
33
|
+
const params = {};
|
|
34
|
+
const results = await commands.execute('getManyBlocks', params);
|
|
35
|
+
reply.code(200).send({ blocks: results });
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create new block
|
|
41
|
+
*/
|
|
42
|
+
create: {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
url: '/blocks/',
|
|
45
|
+
preHandler: async (request, reply) => authorise.create(request, reply),
|
|
46
|
+
handler: async (request, reply) => {
|
|
47
|
+
const params = {
|
|
48
|
+
blocks: [request.body.block]
|
|
49
|
+
};
|
|
50
|
+
const results = await commands.execute('createBlocks', params);
|
|
51
|
+
reply.code(200).send({ blocks: results });
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Update block
|
|
57
|
+
*/
|
|
58
|
+
update: {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
url: '/blocks/:id',
|
|
61
|
+
preHandler: async (request, reply) => authorise.update(request, reply),
|
|
62
|
+
handler: async (request, reply) => {
|
|
63
|
+
const params = {
|
|
64
|
+
id: request.params.id,
|
|
65
|
+
newBlockData: request.body.block
|
|
66
|
+
};
|
|
67
|
+
const result = await commands.execute('updateBlock', params);
|
|
68
|
+
reply.code(200).send({ page: result });
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Delete block
|
|
74
|
+
*/
|
|
75
|
+
delete: {
|
|
76
|
+
method: 'DELETE',
|
|
77
|
+
url: '/blocks/:id',
|
|
78
|
+
preHandler: async (request, reply) => authorise.delete(request, reply),
|
|
79
|
+
handler: async (request, reply) => {
|
|
80
|
+
const params = {
|
|
81
|
+
id: request.params.id
|
|
82
|
+
};
|
|
83
|
+
await commands.execute('deleteBlock', params);
|
|
84
|
+
reply.code(200).send({ message: 'Block deleted' });
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { commands } from '@gnar-engine/core';
|
|
2
|
+
|
|
3
|
+
export const messageHandlers = {
|
|
4
|
+
|
|
5
|
+
getPage: async (payload) => {
|
|
6
|
+
let result;
|
|
7
|
+
if (payload.data?.id) {
|
|
8
|
+
result = await commands.execute('getSinglePage', {
|
|
9
|
+
id: payload.data.id
|
|
10
|
+
});
|
|
11
|
+
} else if (payload.data?.email) {
|
|
12
|
+
result = await commands.execute('getSinglePage', {
|
|
13
|
+
email: payload.data.email
|
|
14
|
+
});
|
|
15
|
+
} else {
|
|
16
|
+
throw new Error('No page ID or email provided');
|
|
17
|
+
}
|
|
18
|
+
if (!result) {
|
|
19
|
+
throw new Error('Page not found');
|
|
20
|
+
}
|
|
21
|
+
return { page: result };
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
getManyPages: async (payload) => {
|
|
25
|
+
const results = await commands.execute('getManyPages', {});
|
|
26
|
+
return { pages: results };
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
createPage: async (payload) => {
|
|
30
|
+
const results = await commands.execute('createPages', {
|
|
31
|
+
pages: [payload.data.page]
|
|
32
|
+
});
|
|
33
|
+
return { pages: results };
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
updatePage: async (payload) => {
|
|
37
|
+
const result = await commands.execute('updatePage', {
|
|
38
|
+
id: payload.data.id,
|
|
39
|
+
newPageData: payload.data
|
|
40
|
+
});
|
|
41
|
+
return { page: result };
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
deletePage: async (payload) => {
|
|
45
|
+
await commands.execute('deletePage', {
|
|
46
|
+
id: payload.data.id
|
|
47
|
+
});
|
|
48
|
+
return { message: 'Page deleted' };
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
};
|