@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.
- package/bootstrap/deploy.localdev.yml +44 -3
- package/bootstrap/secrets.localdev.yml +20 -5
- package/bootstrap/services/control/src/config.js +4 -0
- package/bootstrap/services/notification/Dockerfile +2 -2
- package/bootstrap/services/notification/package.json +14 -32
- package/bootstrap/services/notification/src/app.js +50 -48
- package/bootstrap/services/notification/src/commands/notification.handler.js +96 -0
- package/bootstrap/services/notification/src/config.js +55 -12
- package/bootstrap/services/notification/src/controllers/http.controller.js +87 -0
- package/bootstrap/services/notification/src/controllers/message.controller.js +39 -70
- package/bootstrap/services/notification/src/db/migrations/01-init.js +50 -0
- package/bootstrap/services/notification/src/db/migrations/02-notification-service-init.js +23 -0
- package/bootstrap/services/notification/src/policies/notification.policy.js +49 -0
- package/bootstrap/services/notification/src/schema/notification.schema.js +17 -0
- package/bootstrap/services/notification/src/services/notification.service.js +32 -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/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 +70 -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 +35 -21
- 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 +31 -0
- package/install-from-clone.sh +30 -0
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/config.js +8 -0
- package/src/dev/commands.js +11 -3
- package/src/dev/dev.service.js +164 -64
- package/src/helpers/helpers.js +24 -0
- package/src/profiles/command.js +41 -0
- package/src/profiles/profiles.client.js +23 -0
- 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 +58 -2
- package/src/scaffolder/scaffolder.handler.js +164 -72
- package/templates/entity/src/commands/{{entityName}}.handler.js.hbs +94 -0
- package/templates/entity/src/controllers/{{entityName}}.http.controller.js.hbs +87 -0
- package/templates/entity/src/mysql.db/migrations/03-{{entityName}}-entity-init.js.hbs +23 -0
- package/templates/entity/src/policies/{{entityName}}.policy.js.hbs +49 -0
- package/templates/entity/src/schema/{{entityName}}.schema.js.hbs +17 -0
- package/templates/entity/src/services/mongodb.{{entityName}}.service.js.hbs +70 -0
- package/templates/entity/src/services/mysql.{{entityName}}.service.js.hbs +27 -0
- 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/notification/Dockerfile.prod +0 -37
- package/bootstrap/services/notification/README.md +0 -3
- package/bootstrap/services/notification/src/commands/command-bus.js +0 -20
- package/bootstrap/services/notification/src/commands/handlers/control.handler.js +0 -18
- package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +0 -157
- package/bootstrap/services/notification/src/services/logger.service.js +0 -16
- package/bootstrap/services/notification/src/services/ses.service.js +0 -23
- package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +0 -136
- package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +0 -87
- package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +0 -132
- package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +0 -77
- package/bootstrap/services/user/src/tests/user.test.js +0 -126
- /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();
|
|
@@ -25,7 +25,11 @@ export const config = {
|
|
|
25
25
|
type: 'mongodb',
|
|
26
26
|
|
|
27
27
|
// MongoDB
|
|
28
|
-
|
|
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,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
|
-
}
|