@gnar-engine/cli 1.0.5 → 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 (47) hide show
  1. package/bootstrap/deploy.localdev.yml +14 -0
  2. package/bootstrap/secrets.localdev.yml +7 -3
  3. package/bootstrap/services/notification/Dockerfile +2 -2
  4. package/bootstrap/services/notification/package.json +14 -32
  5. package/bootstrap/services/notification/src/app.js +50 -48
  6. package/bootstrap/services/notification/src/commands/notification.handler.js +96 -0
  7. package/bootstrap/services/notification/src/config.js +55 -12
  8. package/bootstrap/services/notification/src/controllers/http.controller.js +87 -0
  9. package/bootstrap/services/notification/src/controllers/message.controller.js +39 -70
  10. package/bootstrap/services/notification/src/db/migrations/01-init.js +50 -0
  11. package/bootstrap/services/notification/src/db/migrations/02-notification-service-init.js +23 -0
  12. package/bootstrap/services/notification/src/policies/notification.policy.js +49 -0
  13. package/bootstrap/services/notification/src/schema/notification.schema.js +17 -0
  14. package/bootstrap/services/notification/src/services/notification.service.js +32 -0
  15. package/bootstrap/services/portal/src/services/client.js +3 -0
  16. package/bootstrap/services/user/src/commands/user.handler.js +35 -18
  17. package/bootstrap/services/user/src/tests/commands/user.test.js +15 -6
  18. package/install-from-clone.sh +1 -1
  19. package/package.json +1 -1
  20. package/src/cli.js +0 -6
  21. package/src/config.js +8 -0
  22. package/src/dev/commands.js +2 -2
  23. package/src/dev/dev.service.js +19 -6
  24. package/src/helpers/helpers.js +24 -0
  25. package/src/profiles/command.js +41 -0
  26. package/src/profiles/profiles.client.js +23 -0
  27. package/src/scaffolder/commands.js +57 -1
  28. package/src/scaffolder/scaffolder.handler.js +127 -60
  29. package/templates/entity/src/commands/{{entityName}}.handler.js.hbs +94 -0
  30. package/templates/entity/src/controllers/{{entityName}}.http.controller.js.hbs +87 -0
  31. package/templates/entity/src/mysql.db/migrations/03-{{entityName}}-entity-init.js.hbs +23 -0
  32. package/templates/entity/src/policies/{{entityName}}.policy.js.hbs +49 -0
  33. package/templates/entity/src/schema/{{entityName}}.schema.js.hbs +17 -0
  34. package/templates/entity/src/services/mongodb.{{entityName}}.service.js.hbs +70 -0
  35. package/templates/entity/src/services/mysql.{{entityName}}.service.js.hbs +27 -0
  36. package/bootstrap/services/notification/Dockerfile.prod +0 -37
  37. package/bootstrap/services/notification/README.md +0 -3
  38. package/bootstrap/services/notification/src/commands/command-bus.js +0 -20
  39. package/bootstrap/services/notification/src/commands/handlers/control.handler.js +0 -18
  40. package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +0 -157
  41. package/bootstrap/services/notification/src/services/logger.service.js +0 -16
  42. package/bootstrap/services/notification/src/services/ses.service.js +0 -23
  43. package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +0 -136
  44. package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +0 -87
  45. package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +0 -132
  46. package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +0 -77
  47. package/bootstrap/services/notification/src/tests/notification.test.js +0 -0
@@ -3,7 +3,7 @@ import fs from 'fs';
3
3
  import yaml from 'js-yaml';
4
4
  import { profiles } from '../profiles/profiles.client.js';
5
5
  import { helpers } from '../helpers/helpers.js';
6
- import { directories } from '../cli.js';
6
+ import { directories } from '../config.js';
7
7
  import Handlebars from 'handlebars';
8
8
 
9
9
 
@@ -36,67 +36,21 @@ export const scaffolder = {
36
36
 
37
37
  // Get all files in the templates directory
38
38
  const templateFiles = scaffolder.getAllTemplateFiles({
39
- dir: directories.scaffolderTemplates,
40
- baseDir: directories.scaffolderTemplates
39
+ dir: directories.scaffolderServiceTemplates,
40
+ baseDir: directories.scaffolderServiceTemplates
41
41
  });
42
42
 
43
- // Register Handlebars helpers
44
- Object.entries(helpers).forEach(([name, fn]) => {
45
- Handlebars.registerHelper(name, fn);
46
- });
47
-
48
- // Write the files to the service directory
49
- templateFiles.forEach(file => {
50
- let sourcePath;
51
- let targetPath;
52
- const templateArgs = {
53
- serviceName,
54
- database
55
- };
56
-
57
- let fileRelativePath = file.relativePath;
58
-
59
- // Database specific
60
- if (fileRelativePath.includes('mongodb.')) {
61
- if (database !== 'mongodb') {
62
- return;
63
- } else {
64
- fileRelativePath = fileRelativePath.replace('mongodb.', '');
65
- }
66
- }
67
-
68
- if (fileRelativePath.includes('mysql.')) {
69
- if (database !== 'mysql') {
70
- return;
71
- } else {
72
- fileRelativePath = fileRelativePath.replace('mysql.', '');
73
- }
74
- }
43
+ // scaffold the hbs templates
44
+ const templateArgs = {
45
+ serviceName,
46
+ database
47
+ };
75
48
 
76
- switch (file.extension) {
77
- case '.hbs':
78
- // Compile the Handlebars template for content
79
- const templateContent = fs.readFileSync(file.fullPath, 'utf8');
80
- const compiledTemplate = Handlebars.compile(templateContent);
81
- const renderedContent = compiledTemplate(templateArgs);
82
-
83
- // Compile the Handlebars template for the filename (excluding .hbs)
84
- const filenameTemplate = Handlebars.compile(fileRelativePath.replace(/\.hbs$/, ''));
85
- const renderedFilename = filenameTemplate(templateArgs);
86
- targetPath = path.join(serviceDir, renderedFilename);
87
-
88
- // Ensure directory exists
89
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
90
- fs.writeFileSync(targetPath, renderedContent, 'utf8');
91
- break;
92
- default:
93
- // By default, copy the file to the service directory
94
- sourcePath = file.fullPath;
95
- targetPath = path.join(serviceDir, fileRelativePath);
96
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
97
- fs.copyFileSync(sourcePath, targetPath);
98
- break;
99
- }
49
+ scaffolder.scaffoldHandlebarTemplates({
50
+ templateFiles: templateFiles,
51
+ serviceDir: serviceDir,
52
+ database: database,
53
+ templateArgs: templateArgs
100
54
  });
101
55
 
102
56
  // Scaffold deploy.yml
@@ -173,8 +127,10 @@ export const scaffolder = {
173
127
 
174
128
  entries.forEach(entry => {
175
129
  const fullPath = path.join(dir, entry.name);
130
+ // if the full path contains src, the 'data' directory can be included
131
+ const includeDataDir = fullPath.includes(`${path.sep}src${path.sep}`) && fullPath.includes('data') ? true : false;
176
132
  if (entry.isDirectory()) {
177
- if (entry.name !== 'node_modules' && entry.name !== 'data') {
133
+ if (entry.name !== 'node_modules' && (entry.name !== 'data' || includeDataDir)) {
178
134
  if (!entry.name.startsWith('.') || entry.name == '.gnarengine') {
179
135
  scaffolder.getAllTemplateFiles({
180
136
  dir: fullPath,
@@ -446,5 +402,116 @@ export const scaffolder = {
446
402
  // write deploy.yml file
447
403
  const deployYmlContent = yaml.dump(deploy);
448
404
  fs.writeFileSync(deployPath, deployYmlContent, 'utf8');
405
+ },
406
+
407
+ /**
408
+ * Create new entity in existing service
409
+ *
410
+ * @param {object} param
411
+ * @param {string} param.entityName - The name of the entity to create
412
+ * @param {string} param.inService - The service in which to add the entity
413
+ * @param {string} param.serviceDir - The service directory where the entity will be created
414
+ * @param {string} param.database - The database type (e.g., 'mysql', 'mongodb')
415
+ * @returns {object} - An object containing a success message
416
+ */
417
+ createNewEntity: ({ entityName, inService, serviceDir, database }) => {
418
+
419
+ entityName = entityName.toLowerCase();
420
+
421
+ // validate serviceDir exists
422
+ if (!fs.existsSync(serviceDir)) {
423
+ throw new Error(`Service directory "${serviceDir}" does not exist`);
424
+ }
425
+
426
+ // Get all files in the templates directory
427
+ const templateFiles = scaffolder.getAllTemplateFiles({
428
+ dir: directories.scaffolderEntityTemplates,
429
+ baseDir: directories.scaffolderEntityTemplates
430
+ });
431
+
432
+ // scaffold the hbs templates
433
+ const templateArgs = {
434
+ entityName: entityName,
435
+ serviceName: inService,
436
+ database: database
437
+ };
438
+
439
+ scaffolder.scaffoldHandlebarTemplates({
440
+ templateFiles: templateFiles,
441
+ serviceDir: serviceDir,
442
+ database: database,
443
+ templateArgs: templateArgs
444
+ });
445
+ },
446
+
447
+ /**
448
+ * Scaffold handlebar templates
449
+ *
450
+ * @param {object} param
451
+ * @param {string} param.templateFiles - The list of template files
452
+ * @param {string} param.serviceDir - The service directory
453
+ * @param {string} param.serviceName - The service name
454
+ * @param {string} param.database - The database type
455
+ * @param {object} param.templateArgs - The template arguments
456
+ */
457
+ scaffoldHandlebarTemplates: function ({ templateFiles, serviceDir, serviceName, database, templateArgs }) {
458
+ try {
459
+ // Register Handlebars helpers
460
+ Object.entries(helpers).forEach(([name, fn]) => {
461
+ Handlebars.registerHelper(name, fn);
462
+ });
463
+
464
+ // Write the files to the service directory
465
+ templateFiles.forEach(file => {
466
+ let sourcePath;
467
+ let targetPath;
468
+
469
+ let fileRelativePath = file.relativePath;
470
+
471
+ // Database specific
472
+ if (fileRelativePath.includes('mongodb.')) {
473
+ if (database !== 'mongodb') {
474
+ return;
475
+ } else {
476
+ fileRelativePath = fileRelativePath.replace('mongodb.', '');
477
+ }
478
+ }
479
+
480
+ if (fileRelativePath.includes('mysql.')) {
481
+ if (database !== 'mysql') {
482
+ return;
483
+ } else {
484
+ fileRelativePath = fileRelativePath.replace('mysql.', '');
485
+ }
486
+ }
487
+
488
+ switch (file.extension) {
489
+ case '.hbs':
490
+ // Compile the Handlebars template for content
491
+ const templateContent = fs.readFileSync(file.fullPath, 'utf8');
492
+ const compiledTemplate = Handlebars.compile(templateContent);
493
+ const renderedContent = compiledTemplate(templateArgs);
494
+
495
+ // Compile the Handlebars template for the filename (excluding .hbs)
496
+ const filenameTemplate = Handlebars.compile(fileRelativePath.replace(/\.hbs$/, ''));
497
+ const renderedFilename = filenameTemplate(templateArgs);
498
+ targetPath = path.join(serviceDir, renderedFilename);
499
+
500
+ // Ensure directory exists
501
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
502
+ fs.writeFileSync(targetPath, renderedContent, 'utf8');
503
+ break;
504
+ default:
505
+ // By default, copy the file to the service directory
506
+ sourcePath = file.fullPath;
507
+ targetPath = path.join(serviceDir, fileRelativePath);
508
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
509
+ fs.copyFileSync(sourcePath, targetPath);
510
+ break;
511
+ }
512
+ });
513
+ } catch (error) {
514
+ throw new Error('Error scaffolding Handlebars templates: ' + error.message);
515
+ }
449
516
  }
450
517
  }
@@ -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,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
- }