@gnar-engine/cli 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/bootstrap/deploy.localdev.yml +44 -3
  2. package/bootstrap/secrets.localdev.yml +20 -5
  3. package/bootstrap/services/control/src/config.js +4 -0
  4. package/bootstrap/services/notification/Dockerfile +2 -2
  5. package/bootstrap/services/notification/package.json +14 -32
  6. package/bootstrap/services/notification/src/app.js +50 -48
  7. package/bootstrap/services/notification/src/commands/notification.handler.js +96 -0
  8. package/bootstrap/services/notification/src/config.js +55 -12
  9. package/bootstrap/services/notification/src/controllers/http.controller.js +87 -0
  10. package/bootstrap/services/notification/src/controllers/message.controller.js +39 -70
  11. package/bootstrap/services/notification/src/db/migrations/01-init.js +50 -0
  12. package/bootstrap/services/notification/src/db/migrations/02-notification-service-init.js +23 -0
  13. package/bootstrap/services/notification/src/policies/notification.policy.js +49 -0
  14. package/bootstrap/services/notification/src/schema/notification.schema.js +17 -0
  15. package/bootstrap/services/notification/src/services/notification.service.js +32 -0
  16. package/bootstrap/services/page/Dockerfile +23 -0
  17. package/bootstrap/services/page/package.json +16 -0
  18. package/bootstrap/services/page/src/app.js +50 -0
  19. package/bootstrap/services/page/src/commands/block.handler.js +94 -0
  20. package/bootstrap/services/page/src/commands/page.handler.js +167 -0
  21. package/bootstrap/services/page/src/config.js +62 -0
  22. package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
  23. package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
  24. package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
  25. package/bootstrap/services/page/src/policies/block.policy.js +50 -0
  26. package/bootstrap/services/page/src/policies/page.policy.js +49 -0
  27. package/bootstrap/services/page/src/schema/page.schema.js +139 -0
  28. package/bootstrap/services/page/src/services/block.service.js +83 -0
  29. package/bootstrap/services/page/src/services/page.service.js +83 -0
  30. package/bootstrap/services/portal/Dockerfile +20 -0
  31. package/bootstrap/services/portal/README.md +73 -0
  32. package/bootstrap/services/portal/index.html +13 -0
  33. package/bootstrap/services/portal/nginx.conf +5 -0
  34. package/bootstrap/services/portal/package.json +33 -0
  35. package/bootstrap/services/portal/public/vite.svg +1 -0
  36. package/bootstrap/services/portal/react-router.config.js +7 -0
  37. package/bootstrap/services/portal/src/App.jsx +16 -0
  38. package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
  39. package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
  40. package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
  41. package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
  42. package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
  43. package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
  44. package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
  45. package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
  46. package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
  47. package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
  48. package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
  49. package/bootstrap/services/portal/src/assets/react.svg +1 -0
  50. package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
  51. package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
  52. package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.jsx +81 -0
  53. package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
  54. package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
  55. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
  56. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
  57. package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
  58. package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
  59. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
  60. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
  61. package/bootstrap/services/portal/src/css/style.css +711 -0
  62. package/bootstrap/services/portal/src/data/pages.data.js +10 -0
  63. package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
  64. package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.less +102 -0
  65. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
  66. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
  67. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
  68. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
  69. package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
  70. package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
  71. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
  72. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
  73. package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
  74. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
  75. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
  76. package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
  77. package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
  78. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
  79. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
  80. package/bootstrap/services/portal/src/main.jsx +51 -0
  81. package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
  82. package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
  83. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
  84. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
  85. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
  86. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
  87. package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
  88. package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
  89. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
  90. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
  91. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
  92. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
  93. package/bootstrap/services/portal/src/services/block.js +28 -0
  94. package/bootstrap/services/portal/src/services/client.js +70 -0
  95. package/bootstrap/services/portal/src/services/gravatar.js +14 -0
  96. package/bootstrap/services/portal/src/services/page.js +28 -0
  97. package/bootstrap/services/portal/src/services/storage.js +62 -0
  98. package/bootstrap/services/portal/src/services/user.js +41 -0
  99. package/bootstrap/services/portal/src/slices/authSlice.js +101 -0
  100. package/bootstrap/services/portal/src/store/configureStore.js +10 -0
  101. package/bootstrap/services/portal/src/style/cards.less +57 -0
  102. package/bootstrap/services/portal/src/style/global.less +204 -0
  103. package/bootstrap/services/portal/src/style/icons.less +21 -0
  104. package/bootstrap/services/portal/src/style/inputs.less +52 -0
  105. package/bootstrap/services/portal/src/style/main.less +28 -0
  106. package/bootstrap/services/portal/src/utils/utils.js +9 -0
  107. package/bootstrap/services/portal/vite.config.js +12 -0
  108. package/bootstrap/services/user/src/app.js +6 -1
  109. package/bootstrap/services/user/src/commands/user.handler.js +35 -21
  110. package/bootstrap/services/user/src/config.js +5 -1
  111. package/bootstrap/services/user/src/policies/user.policy.js +3 -1
  112. package/bootstrap/services/user/src/tests/commands/user.test.js +31 -0
  113. package/install-from-clone.sh +30 -0
  114. package/package.json +1 -1
  115. package/src/cli.js +2 -0
  116. package/src/config.js +8 -0
  117. package/src/dev/commands.js +11 -3
  118. package/src/dev/dev.service.js +164 -64
  119. package/src/helpers/helpers.js +24 -0
  120. package/src/profiles/command.js +41 -0
  121. package/src/profiles/profiles.client.js +23 -0
  122. package/src/provisioner/Dockerfile +27 -0
  123. package/src/provisioner/package.json +19 -0
  124. package/src/provisioner/src/app.js +56 -0
  125. package/src/provisioner/src/services/mongodb.js +58 -0
  126. package/src/provisioner/src/services/mysql.js +51 -0
  127. package/src/provisioner/src/services/secrets.js +84 -0
  128. package/src/scaffolder/commands.js +58 -2
  129. package/src/scaffolder/scaffolder.handler.js +164 -72
  130. package/templates/entity/src/commands/{{entityName}}.handler.js.hbs +94 -0
  131. package/templates/entity/src/controllers/{{entityName}}.http.controller.js.hbs +87 -0
  132. package/templates/entity/src/mysql.db/migrations/03-{{entityName}}-entity-init.js.hbs +23 -0
  133. package/templates/entity/src/policies/{{entityName}}.policy.js.hbs +49 -0
  134. package/templates/entity/src/schema/{{entityName}}.schema.js.hbs +17 -0
  135. package/templates/entity/src/services/mongodb.{{entityName}}.service.js.hbs +70 -0
  136. package/templates/entity/src/services/mysql.{{entityName}}.service.js.hbs +27 -0
  137. package/templates/service/src/app.js.hbs +12 -1
  138. package/templates/service/src/commands/{{serviceName}}.handler.js.hbs +1 -1
  139. package/templates/service/src/mongodb.config.js.hbs +5 -1
  140. package/templates/service/src/mysql.config.js.hbs +4 -0
  141. package/bootstrap/services/notification/Dockerfile.prod +0 -37
  142. package/bootstrap/services/notification/README.md +0 -3
  143. package/bootstrap/services/notification/src/commands/command-bus.js +0 -20
  144. package/bootstrap/services/notification/src/commands/handlers/control.handler.js +0 -18
  145. package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +0 -157
  146. package/bootstrap/services/notification/src/services/logger.service.js +0 -16
  147. package/bootstrap/services/notification/src/services/ses.service.js +0 -23
  148. package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +0 -136
  149. package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +0 -87
  150. package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +0 -132
  151. package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +0 -77
  152. package/bootstrap/services/user/src/tests/user.test.js +0 -126
  153. /package/bootstrap/services/{notification/src/tests/notification.test.js → portal/src/components/CustomSelect/CustomSelect.less} +0 -0
@@ -0,0 +1,50 @@
1
+ import { logger, db } from '@gnar-engine/core';
2
+
3
+ /**
4
+ * Up
5
+ */
6
+ export const up = async () => {
7
+ await initDatabaseTables();
8
+ }
9
+
10
+ /**
11
+ * Down
12
+ */
13
+ export const down = async () => {
14
+ await dropDatabaseTables();
15
+ }
16
+
17
+ /**
18
+ * Create all tables
19
+ */
20
+ export const initDatabaseTables = async () => {
21
+
22
+ // Migrations table
23
+ logger.info("Creating migrations table");
24
+ const createMigrationsTableQuery = `
25
+ CREATE TABLE migrations (
26
+ id INT AUTO_INCREMENT PRIMARY KEY,
27
+ name VARCHAR(255) NOT NULL UNIQUE,
28
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
29
+ )`;
30
+ await db.query(createMigrationsTableQuery);
31
+
32
+ // Seeders table
33
+ logger.info("Creating seeders table");
34
+ const createSeedersTableQuery = `
35
+ CREATE TABLE seeders (
36
+ id INT AUTO_INCREMENT PRIMARY KEY,
37
+ name VARCHAR(255) NOT NULL UNIQUE,
38
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
39
+ )`;
40
+ await db.query(createSeedersTableQuery);
41
+ }
42
+
43
+ /**
44
+ * Drop all tables
45
+ */
46
+ export const dropDatabaseTables = async () => {
47
+ logger.info('Dropping tables');
48
+ await db.query('DROP TABLE IF EXISTS migrations');
49
+ await db.query('DROP TABLE IF EXISTS seeders');
50
+ }
@@ -0,0 +1,23 @@
1
+ import { logger, db } from '@gnar-engine/core';
2
+
3
+ /**
4
+ * Up
5
+ */
6
+ export const up = async () => {
7
+ logger.info('Creating table: notifications');
8
+ await db.query(`
9
+ CREATE TABLE notifications (
10
+ id INT AUTO_INCREMENT PRIMARY KEY,
11
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
12
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
13
+ )
14
+ `);
15
+ }
16
+
17
+ /**
18
+ * Down
19
+ */
20
+ export const down = async () => {
21
+ logger.info('Dropping table: notifications');
22
+ await db.query('DROP TABLE IF EXISTS notifications');
23
+ }
@@ -0,0 +1,49 @@
1
+ import { config } from '../config.js';
2
+
3
+ export const authorise = {
4
+
5
+ /**
6
+ * Authorise get single notification
7
+ */
8
+ getSingle: async (request, reply) => {
9
+ if (!request.user || request.user.role !== 'service_admin') {
10
+ reply.code(403).send({error: 'not authorised'});
11
+ }
12
+ },
13
+
14
+ /**
15
+ * Authorise get many notifications
16
+ */
17
+ getMany: async (request, reply) => {
18
+ if (!request.user || request.user.role !== 'service_admin') {
19
+ reply.code(403).send({error: 'not authorised'});
20
+ }
21
+ },
22
+
23
+ /**
24
+ * Authorise create notifications
25
+ */
26
+ create: async (request, reply) => {
27
+ if (!request.user || request.user.role !== 'service_admin') {
28
+ reply.code(403).send({error: 'not authorised'});
29
+ }
30
+ },
31
+
32
+ /**
33
+ * Authorise update notification
34
+ */
35
+ update: async (request, reply) => {
36
+ if (!request.user || request.user.role !== 'service_admin') {
37
+ reply.code(403).send({error: 'not authorised'});
38
+ }
39
+ },
40
+
41
+ /**
42
+ * Authorise delete notification
43
+ */
44
+ delete: async (request, reply) => {
45
+ if (!request.user || request.user.role !== 'service_admin') {
46
+ reply.code(403).send({error: 'not authorised'});
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,17 @@
1
+ import { schema } from '@gnar-engine/core';
2
+ import { config } from '../config.js';
3
+
4
+ export const notificationSchema = {
5
+ schemaName: 'notificationService.notificationSchema',
6
+ schema: {
7
+ type: 'object',
8
+ properties: {
9
+ // Add your properties here
10
+
11
+ },
12
+ required: [],
13
+ additionalProperties: false
14
+ }
15
+ };
16
+
17
+ export const validateNotification = schema.compile(notificationSchema);
@@ -0,0 +1,32 @@
1
+ import { db } from '@gnar-engine/core';
2
+
3
+ export const notification = {
4
+ async getById({ id }) {
5
+ const [result] = await db.query('SELECT * FROM notifications WHERE id = ?', [id]);
6
+ return result || null;
7
+ },
8
+
9
+ async getByEmail({ email }) {
10
+ // Placeholder: implement if your service uses email
11
+ return null;
12
+ },
13
+
14
+ async getAll() {
15
+ return await db.query('SELECT * FROM notifications');
16
+ },
17
+
18
+ async create(data) {
19
+ const { insertId } = await db.query('INSERT INTO notifications (created_at, updated_at) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)');
20
+ return await this.getById({ id: insertId });
21
+ },
22
+
23
+ async update({ id, ...data }) {
24
+ await db.query('UPDATE notifications SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', [id]);
25
+ return await this.getById({ id });
26
+ },
27
+
28
+ async delete({ id }) {
29
+ await db.query('DELETE FROM notifications WHERE id = ?', [id]);
30
+ return true;
31
+ },
32
+ };
@@ -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
+ }