@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,94 @@
1
+ import { commands, logger, error } from '@gnar-engine/core';
2
+ import { {{entityName}} } from '../services/{{entityName}}.service.js';
3
+ import { config } from '../config.js';
4
+ import { validate{{pascalCase entityName}} } from '../schema/{{entityName}}.schema.js';
5
+
6
+
7
+ /**
8
+ * Get single {{entityName}}
9
+ */
10
+ commands.register('{{serviceName}}Service.getSingle{{pascalCase entityName}}', async ({id}) => {
11
+ if (id) {
12
+ return await {{entityName}}.getById({id: id});
13
+ } else {
14
+ throw new error.badRequest('{{pascalCase entityName}} id required');
15
+ }
16
+ });
17
+
18
+ /**
19
+ * Get many {{lowerCasePlural entityName}}
20
+ */
21
+ commands.register('{{serviceName}}Service.getMany{{pascalCasePlural entityName}}', async ({}) => {
22
+ return await {{entityName}}.getAll();
23
+ });
24
+
25
+ /**
26
+ * Create {{lowerCasePlural entityName}}
27
+ */
28
+ commands.register('{{serviceName}}Service.create{{pascalCasePlural entityName}}', async ({ {{lowerCasePlural entityName}} }) => {
29
+ const validationErrors = [];
30
+ let createdNew{{pascalCasePlural entityName}} = [];
31
+
32
+ for (const newData of {{lowerCasePlural entityName}}) {
33
+ const { errors } = validate{{pascalCase entityName}}(newData);
34
+ if (errors?.length) {
35
+ validationErrors.push(errors);
36
+ continue;
37
+ }
38
+
39
+ const created = await {{entityName}}.create(newData);
40
+ createdNew{{pascalCasePlural entityName}}.push(created);
41
+ }
42
+
43
+ if (validationErrors.length) {
44
+ throw new error.badRequest(`Invalid {{entityName}} data: ${validationErrors}`);
45
+ }
46
+
47
+ return createdNew{{pascalCasePlural entityName}};
48
+ });
49
+
50
+ /**
51
+ * Update {{entityName}}
52
+ */
53
+ commands.register('{{serviceName}}Service.update{{pascalCase entityName}}', async ({id, new{{pascalCase entityName}}Data}) => {
54
+
55
+ const validationErrors = [];
56
+
57
+ if (!id) {
58
+ throw new error.badRequest('{{pascalCase entityName}} ID required');
59
+ }
60
+
61
+ const obj = await {{entityName}}.getById({id: id});
62
+
63
+ if (!obj) {
64
+ throw new error.notFound('{{pascalCase entityName}} not found');
65
+ }
66
+
67
+ delete new{{pascalCase entityName}}Data.id;
68
+
69
+ const { errors } = validate{{pascalCase entityName}}Update(new{{pascalCase entityName}}Data);
70
+
71
+ if (errors?.length) {
72
+ validationErrors.push(errors);
73
+ }
74
+
75
+ if (validationErrors.length) {
76
+ throw new error.badRequest(`Invalid {{entityName}} data: ${validationErrors}`);
77
+ }
78
+
79
+ return await {{entityName}}.update({
80
+ id: id,
81
+ updatedData: new{{pascalCase entityName}}Data
82
+ });
83
+ });
84
+
85
+ /**
86
+ * Delete {{entityName}}
87
+ */
88
+ commands.register('{{serviceName}}Service.delete{{pascalCase entityName}}', async ({id}) => {
89
+ const obj = await {{entityName}}.getById({id: id});
90
+ if (!obj) {
91
+ throw new error.notFound('{{pascalCase entityName}} not found');
92
+ }
93
+ return await {{entityName}}.delete({id: id});
94
+ });
@@ -0,0 +1,87 @@
1
+ import { commands } from '@gnar-engine/core';
2
+ import { authorise } from '../policies/{{entityName}}.policy.js';
3
+
4
+ /**
5
+ * HTTP controller
6
+ */
7
+ export const httpController = {
8
+
9
+ /**
10
+ * Get single {{entityName}}
11
+ */
12
+ getSingle: {
13
+ method: 'GET',
14
+ url: '/{{lowerCasePlural entityName}}/: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('getSingle{{pascalCase entityName}}', params);
21
+ reply.code(200).send({ {{entityName}}: result });
22
+ }
23
+ },
24
+
25
+ /**
26
+ * Get multiple {{lowerCasePlural entityName}}
27
+ */
28
+ getMany: {
29
+ method: 'GET',
30
+ url: '/{{lowerCasePlural entityName}}/',
31
+ preHandler: async (request, reply) => authorise.getMany(request, reply),
32
+ handler: async (request, reply) => {
33
+ const params = {};
34
+ const results = await commands.execute('getMany{{pascalCasePlural entityName}}', params);
35
+ reply.code(200).send({ {{lowerCasePlural entityName}}: results });
36
+ }
37
+ },
38
+
39
+ /**
40
+ * Create new {{entityName}}
41
+ */
42
+ create: {
43
+ method: 'POST',
44
+ url: '/{{lowerCasePlural entityName}}/',
45
+ preHandler: async (request, reply) => authorise.create(request, reply),
46
+ handler: async (request, reply) => {
47
+ const params = {
48
+ {{lowerCasePlural entityName}}: [request.body.{{entityName}}]
49
+ };
50
+ const results = await commands.execute('create{{pascalCasePlural entityName}}', params);
51
+ reply.code(200).send({ {{lowerCasePlural entityName}}: results });
52
+ },
53
+ },
54
+
55
+ /**
56
+ * Update {{entityName}}
57
+ */
58
+ update: {
59
+ method: 'POST',
60
+ url: '/{{lowerCasePlural entityName}}/:id',
61
+ preHandler: async (request, reply) => authorise.update(request, reply),
62
+ handler: async (request, reply) => {
63
+ const params = {
64
+ id: request.params.id,
65
+ new{{pascalCase entityName}}Data: request.body
66
+ };
67
+ const result = await commands.execute('update{{pascalCase entityName}}', params);
68
+ reply.code(200).send({ {{entityName}}: result });
69
+ },
70
+ },
71
+
72
+ /**
73
+ * Delete {{entityName}}
74
+ */
75
+ delete: {
76
+ method: 'DELETE',
77
+ url: '/{{lowerCasePlural entityName}}/: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('delete{{pascalCase entityName}}', params);
84
+ reply.code(200).send({ message: '{{pascalCase entityName}} deleted' });
85
+ },
86
+ },
87
+ }
@@ -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: {{lowerCasePlural entityName}}');
8
+ await db.query(`
9
+ CREATE TABLE {{lowerCasePlural entityName}} (
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: {{lowerCasePlural entityName}}');
22
+ await db.query('DROP TABLE IF EXISTS {{lowerCasePlural entityName}}');
23
+ }
@@ -0,0 +1,49 @@
1
+ import { config } from '../config.js';
2
+
3
+ export const authorise = {
4
+
5
+ /**
6
+ * Authorise get single {{entityName}}
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 {{lowerCasePlural entityName}}
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 {{lowerCasePlural entityName}}
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 {{entityName}}
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 {{entityName}}
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 {{entityName}}Schema = {
5
+ schemaName: '{{serviceName}}Service.{{entityName}}Schema',
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 validate{{pascalCase entityName}} = schema.compile({{entityName}}Schema);
@@ -0,0 +1,70 @@
1
+ import { db, logger } from '@gnar-engine/core';
2
+ import { ObjectId } from 'mongodb';
3
+
4
+ export const {{entityName}} = {
5
+
6
+ // Get all {{lowerCasePlural entityName}}
7
+ getAll: async () => {
8
+ try {
9
+ const items = await db.collection('{{lowerCasePlural entityName}}').find().toArray();
10
+ return items;
11
+ } catch (error) {
12
+ logger.error("Error fetching {{lowerCasePlural entityName}}:", error);
13
+ throw error;
14
+ }
15
+ },
16
+
17
+ // Create a {{lowerCase entityName}}
18
+ create: async (data) => {
19
+ try {
20
+ const collection = db.collection('{{lowerCasePlural entityName}}');
21
+ const result = await collection.insertOne(data);
22
+ return await collection.findOne({ _id: result.insertedId });
23
+ } catch (error) {
24
+ logger.error("Error creating {{lowerCase entityName}}:", error);
25
+ throw error;
26
+ }
27
+ },
28
+
29
+ // Get a {{lowerCase entityName}} by ID
30
+ getById: async ({ id }) => {
31
+ try {
32
+ const collection = db.collection('{{lowerCasePlural entityName}}');
33
+ const objectId = new ObjectId(id);
34
+ const item = await collection.findOne({ _id: objectId });
35
+ return item;
36
+ } catch (error) {
37
+ logger.error("Error fetching {{lowerCase entityName}}:", error);
38
+ throw error;
39
+ }
40
+ },
41
+
42
+ // Update a {{lowerCase entityName}}
43
+ update: async ({ id, updatedData }) => {
44
+ try {
45
+ const collection = db.collection('{{lowerCasePlural entityName}}');
46
+ const objectId = new ObjectId(id);
47
+ const result = await collection.updateOne(
48
+ { _id: objectId },
49
+ { $set: updatedData }
50
+ );
51
+ return result.modifiedCount > 0;
52
+ } catch (error) {
53
+ logger.error("Error updating {{lowerCase entityName}}:", error);
54
+ throw error;
55
+ }
56
+ },
57
+
58
+ // Delete a {{lowerCase entityName}}
59
+ delete: async ({ id }) => {
60
+ try {
61
+ const collection = db.collection('{{lowerCasePlural entityName}}');
62
+ const objectId = new ObjectId(id);
63
+ const result = await collection.deleteOne({ _id: objectId });
64
+ return result.deletedCount > 0;
65
+ } catch (error) {
66
+ logger.error("Error deleting {{lowerCase entityName}}:", error);
67
+ throw error;
68
+ }
69
+ }
70
+ };
@@ -0,0 +1,27 @@
1
+ import { db } from '@gnar-engine/core';
2
+
3
+ export const {{entityName}} = {
4
+ async getById({ id }) {
5
+ const [result] = await db.query('SELECT * FROM {{lowerCasePlural entityName}} WHERE id = ?', [id]);
6
+ return result || null;
7
+ },
8
+
9
+ async getAll() {
10
+ return await db.query('SELECT * FROM {{lowerCasePlural entityName}}');
11
+ },
12
+
13
+ async create(data) {
14
+ const { insertId } = await db.query('INSERT INTO {{lowerCasePlural entityName}} (created_at, updated_at) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)');
15
+ return await this.getById({ id: insertId });
16
+ },
17
+
18
+ async update({ id, ...data }) {
19
+ await db.query('UPDATE {{lowerCasePlural entityName}} SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', [id]);
20
+ return await this.getById({ id });
21
+ },
22
+
23
+ async delete({ id }) {
24
+ await db.query('DELETE FROM {{lowerCasePlural entityName}} WHERE id = ?', [id]);
25
+ return true;
26
+ },
27
+ };
@@ -1,4 +1,4 @@
1
- import { message, http, logger, db } from '@gnar-engine/core';
1
+ import { message, http, logger, db, registerService, webSockets, test } from '@gnar-engine/core';
2
2
  import { config } from './config.js';
3
3
  import { messageHandlers } from './controllers/message.controller.js';
4
4
  import { httpController as {{lowerCase serviceName}}PlatformHttpController } from './controllers/http.controller.js';
@@ -26,6 +26,9 @@ export const initService = async () => {
26
26
  handlers: messageHandlers
27
27
  });
28
28
 
29
+ // Initialise websocket client & server
30
+ await webSockets.init(config.webSockets, config.serviceName);
31
+
29
32
  // Register http routes
30
33
  await http.registerRoutes({
31
34
  controllers: [
@@ -36,7 +39,15 @@ export const initService = async () => {
36
39
  // Start the HTTP server
37
40
  await http.start();
38
41
 
42
+ // Register service with control service
43
+ await registerService();
44
+
39
45
  logger.info('G n a r E n g i n e | {{capitaliseFirstLetter serviceName}} Service initialised successfully.');
46
+
47
+ // Tests
48
+ if (config.environment === 'test' && config.runTests) {
49
+ test.runCommandTests({config});
50
+ }
40
51
  }
41
52
 
42
53
  initService();
@@ -80,7 +80,7 @@ commands.register('{{serviceName}}Service.update{{pascalCase serviceName}}', asy
80
80
 
81
81
  return await {{serviceName}}.update({
82
82
  id: id,
83
- ...new{{pascalCase serviceName}}Data
83
+ updatedData: new{{pascalCase serviceName}}Data
84
84
  });
85
85
  });
86
86
 
@@ -25,7 +25,11 @@ export const config = {
25
25
  type: 'mongodb',
26
26
 
27
27
  // MongoDB
28
- connectionUrl: process.env.{{upperCase serviceName}}_MONGO_URL,
28
+ host: process.env.{{upperCase serviceName}}_MONGO_HOST,
29
+ database: process.env.{{upperCase serviceName}}_MONGO_DATABASE,
30
+ user: process.env.{{upperCase serviceName}}_MONGO_USER,
31
+ password: process.env.{{upperCase serviceName}}_MONGO_PASSWORD,
32
+ port: process.env.{{upperCase serviceName}}_MONGO_PORT || 27017,
29
33
  connectionArgs: {},
30
34
  },
31
35
 
@@ -5,6 +5,10 @@ export const config = {
5
5
  // service name
6
6
  serviceName: '{{serviceName}}Service',
7
7
 
8
+ // environment
9
+ environment: process.env.{{upperCase serviceName}}_NODE_ENV || 'dev',
10
+ runTests: process.env.{{upperCase serviceName}}_RUN_TESTS || false,
11
+
8
12
  // microservice | modular-monolith
9
13
  architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
10
14
 
@@ -1,37 +0,0 @@
1
- # Stage 1: Builder
2
- FROM node:20-alpine AS builder
3
-
4
- WORKDIR /app
5
-
6
- # Copy app source
7
- COPY ./services/notification/src ./src
8
- COPY ./Lib ./Lib
9
-
10
- # Copy environment variables (do this later in build only if needed)
11
- COPY ./.env.production .env
12
-
13
- # Copy package files and install deps
14
- COPY ./services/notification/package*.json ./
15
- RUN npm install --omit=dev
16
-
17
- # Stage 2: Runtime
18
- FROM node:20-alpine
19
-
20
- WORKDIR /app
21
-
22
- # Copy built app from builder stage
23
- COPY --from=builder /app /app
24
-
25
- # Install system deps
26
- RUN apk add --no-cache ca-certificates wget
27
-
28
- # Install AWS DocumentDB CA certificates
29
- RUN mkdir -p /usr/local/share/ca-certificates && \
30
- wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -O /usr/local/share/ca-certificates/aws-docdb.pem && \
31
- update-ca-certificates
32
-
33
- # Expose port
34
- EXPOSE 4000
35
-
36
- # Start app
37
- CMD ["npm", "run", "start"]
@@ -1,3 +0,0 @@
1
- # Gnar Engine Notification Service
2
-
3
- - Email sending
@@ -1,20 +0,0 @@
1
- /**
2
- * Command bus
3
- */
4
- export const commandBus = {
5
- handlers: new Map(),
6
-
7
- register(commandName, handlerFunction) {
8
- this.handlers.set(commandName, handlerFunction);
9
- },
10
-
11
- async execute(commandName, ...args) {
12
- const handlerFunction = this.handlers.get(commandName);
13
- if (!handlerFunction) {
14
- console.log('handlers', this.handlers);
15
- throw new Error(`Command ${commandName} not registered`);
16
- }
17
-
18
- return await handlerFunction(...args);
19
- }
20
- }
@@ -1,18 +0,0 @@
1
-
2
- /**
3
- * Run seeders
4
- *
5
- * @param {Object} params
6
- * @param {string} params.seeder Name of single seeder to run (optional)
7
- */
8
- export const runSeeders = async ({seeder}) => {
9
- // checkout service has no db
10
- }
11
-
12
- /**
13
- * Internal health check (kills process if it fails)
14
- */
15
- export const internalHealthCheck = async () => {
16
-
17
- // Nothing to check for checkout service
18
- }
@@ -1,157 +0,0 @@
1
- import { logger } from '../../services/logger.service.js';
2
- import { emailSendingService, emailHeaderLogoUrl } from './../../config.js';
3
- import { helpers } from '@gnar-engine/helpers';
4
- import fs from 'fs';
5
- import handlebars from 'handlebars';
6
- import nodemailer from 'nodemailer';
7
- import { getSesClient } from '../../services/ses.service.js';
8
- import { SendEmailCommand } from '@aws-sdk/client-ses';
9
-
10
-
11
- /**
12
- * Send a notification
13
- *
14
- * @param {Object} params
15
- * @param {string} params.templateName - Name of the template file (without extension)
16
- * @param {string} params.to - Recipient email address
17
- * @param {Object} params.params - Parameters to be passed to the template
18
- * @param {string} params.subject - Subject of the email
19
- */
20
- export const sendNotification = async ({ templateName, to, params, subject }) => {
21
- let source;
22
- let template;
23
-
24
- // get the requested template
25
- try {
26
- const workingDir = process.cwd();
27
- const path = workingDir + '/src/templates/' + templateName + '.hbs';
28
- source = fs.readFileSync(path, 'utf8');
29
- } catch (error) {
30
- logger.error('Error reading template file: ' + error.message);
31
- throw new Error('Template not found');
32
- }
33
-
34
- // compile the template
35
- try {
36
- template = handlebars.compile(source);
37
- } catch (error) {
38
- logger.error('Error compiling template: ' + error.message);
39
- throw new Error('Template compilation failed');
40
- }
41
-
42
- // append other params
43
- params = prepareParams(params, templateName);
44
-
45
- // prepare the template
46
- const html = template(params);
47
-
48
- // send the email
49
- switch (emailSendingService) {
50
- case 'SMTP':
51
- await sendSmtpEmail({ to, subject, html });
52
- break;
53
-
54
- case 'SES':
55
- await sendSesEmail({ to, subject, html });
56
- break;
57
-
58
- case 'Direct':
59
- logger.error('Email sending service not implemented: ' + emailSendingService);
60
- throw new Error('Email sending service not implemented');
61
-
62
- default:
63
- logger.error('Invalid email sending service: ' + emailSendingService);
64
- throw new Error('Invalid email sending service');
65
- }
66
- }
67
-
68
- /**
69
- * Send SMTP email
70
- *
71
- * @param {Object} params
72
- * @param {string} params.to - Recipient email address
73
- * @param {string} params.subject - Email subject
74
- * @param {string} params.html - HTML content of the email
75
- */
76
- export const sendSmtpEmail = async ({ to, subject, html }) => {
77
- try {
78
- const transporter = nodemailer.createTransport({
79
- host: process.env.SMTP_HOST,
80
- port: parseInt(process.env.SMTP_PORT || '465'),
81
- secure: true,
82
- auth: {
83
- user: process.env.SMTP_USER,
84
- pass: process.env.SMTP_PASS
85
- }
86
- });
87
-
88
- const mailOptions = {
89
- from: `"Your App Name" <${process.env.SMTP_USER}>`,
90
- to,
91
- subject,
92
- html
93
- };
94
-
95
- await transporter.sendMail(mailOptions);
96
- } catch (error) {
97
- logger.error('SMTP email send error: ' + error.message);
98
- throw new Error('SMTP email failed to send');
99
- }
100
- }
101
-
102
- /**
103
- * Send SES email
104
- *
105
- * @param {Object} params
106
- * @param {string} params.to - Recipient email address
107
- * @param {string} params.subject - Email subject
108
- * @param {string} params.html - HTML content of the email
109
- */
110
- export const sendSesEmail = async ({ to, subject, html }) => {
111
- try {
112
- const sesClient = getSesClient();
113
-
114
- const command = new SendEmailCommand({
115
- Source: process.env.NOTIFICATION_SES_SOURCE_EMAIL,
116
- Destination: {
117
- ToAddresses: [to]
118
- },
119
- Message: {
120
- Subject: {
121
- Data: subject,
122
- Charset: 'UTF-8'
123
- },
124
- Body: {
125
- Html: {
126
- Data: html,
127
- Charset: 'UTF-8'
128
- }
129
- }
130
- }
131
- });
132
-
133
- await sesClient.send(command);
134
- } catch (error) {
135
- logger.error('Error sending email with SES: ' + error.message);
136
- throw new Error('SES email failed to send');
137
- }
138
- }
139
-
140
- /**
141
- * Prepare parameters for the template
142
- *
143
- * @param {Object} params - Parameters to be passed to the template
144
- * @param {string} templateName - Name of the template file (without extension)
145
- * @returns {Object} - Prepared parameters
146
- */
147
- const prepareParams = (params, templateName) => {
148
-
149
- // add shop logo
150
- params.logoUrl = emailHeaderLogoUrl;
151
-
152
- if (params.order?.currency) {
153
- params.currencySymbol = helpers.ecommerce.getCurrencySymbol(params.order.currency);
154
- }
155
-
156
- return params
157
- }