@gnar-engine/cli 1.0.5 → 1.0.7

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 (48) 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 +13 -1
  22. package/src/dev/commands.js +7 -3
  23. package/src/dev/dev.service.js +192 -128
  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/src/services/docker.js +173 -0
  30. package/templates/entity/src/commands/{{entityName}}.handler.js.hbs +94 -0
  31. package/templates/entity/src/controllers/{{entityName}}.http.controller.js.hbs +87 -0
  32. package/templates/entity/src/mysql.db/migrations/03-{{entityName}}-entity-init.js.hbs +23 -0
  33. package/templates/entity/src/policies/{{entityName}}.policy.js.hbs +49 -0
  34. package/templates/entity/src/schema/{{entityName}}.schema.js.hbs +17 -0
  35. package/templates/entity/src/services/mongodb.{{entityName}}.service.js.hbs +70 -0
  36. package/templates/entity/src/services/mysql.{{entityName}}.service.js.hbs +27 -0
  37. package/bootstrap/services/notification/Dockerfile.prod +0 -37
  38. package/bootstrap/services/notification/README.md +0 -3
  39. package/bootstrap/services/notification/src/commands/command-bus.js +0 -20
  40. package/bootstrap/services/notification/src/commands/handlers/control.handler.js +0 -18
  41. package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +0 -157
  42. package/bootstrap/services/notification/src/services/logger.service.js +0 -16
  43. package/bootstrap/services/notification/src/services/ses.service.js +0 -23
  44. package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +0 -136
  45. package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +0 -87
  46. package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +0 -132
  47. package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +0 -77
  48. 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,173 @@
1
+ import Docker from 'dockerode';
2
+ import { Writable } from 'stream';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+
6
+ const docker = new Docker();
7
+
8
+ /**
9
+ * Build Docker image
10
+ *
11
+ * @param {Object} options
12
+ * @param {string} options.context - The build context directory
13
+ * @param {string} options.dockerfile - The Dockerfile path relative to the context
14
+ * @param {string} options.imageTag - The tag to assign to the built image
15
+ */
16
+ export async function buildImage({ context, dockerfile, imageTag }) {
17
+
18
+ console.log('Building image...', imageTag);
19
+
20
+ const tarStream = await docker.buildImage(
21
+ {
22
+ context: context,
23
+ src: fs.readdirSync(context)
24
+ },
25
+ {
26
+ t: imageTag,
27
+ dockerfile
28
+ }
29
+ );
30
+
31
+ await new Promise((resolve, reject) => {
32
+ docker.modem.followProgress(tarStream, (err) => (err ? reject(err) : resolve()));
33
+ });
34
+
35
+ console.log('Built image:', imageTag);
36
+ }
37
+
38
+ /**
39
+ * Create docker container
40
+ *
41
+ * @param {Object} options
42
+ * @param {string} options.name - Container name
43
+ * @param {string} options.image - Docker image to use
44
+ * @param {Array} [options.command] - Command to run in the container
45
+ * @param {Object} [options.env] - Environment variables
46
+ * @param {Object} [options.ports] - Port mappings (containerPort: hostPort)
47
+ * @param {Array} [options.binds] - Volume bindings
48
+ * @param {string} [options.restart] - Restart policy
49
+ * @param {boolean} [options.attach] - Whether to attach to container output
50
+ * @param {string} [options.network] - Network name
51
+ * @returns {Promise<Docker.Container>} - The started container
52
+ */
53
+ export async function createContainer({ name, image, command = [], env = {}, ports = {}, binds = [], restart = 'always', attach = true, network, aliases = [] }) {
54
+
55
+ // remove container first
56
+ try {
57
+ const existingContainer = docker.getContainer(name);
58
+ await existingContainer.inspect();
59
+ await existingContainer.remove({ force: true });
60
+ } catch (err) {
61
+ // Container does not exist, ignore
62
+ }
63
+
64
+ // create container
65
+ const containerConfig = {
66
+ name,
67
+ Image: image,
68
+ Cmd: command,
69
+ Env: Object.entries(env).map(([k, v]) => `${k}=${v}`),
70
+ HostConfig: {
71
+ RestartPolicy: { Name: restart },
72
+ Binds: binds,
73
+ PortBindings: Object.fromEntries(
74
+ Object.entries(ports).map(([cPort, hPort]) => [
75
+ `${cPort}/tcp`,
76
+ [{ HostPort: String(hPort) }]
77
+ ])
78
+ ),
79
+ },
80
+ ExposedPorts: Object.fromEntries(
81
+ Object.keys(ports).map(p => [`${p}/tcp`, {}])
82
+ )
83
+ };
84
+
85
+ const container = await docker.createContainer(containerConfig);
86
+
87
+ // Attach logs before starting the container
88
+ if (attach) {
89
+ const stream = await container.attach({ stream: true, stdout: true, stderr: true, logs: true });
90
+ const stdoutStream = createPrefixStream(name, process.stdout);
91
+ const stderrStream = createPrefixStream(name, process.stderr);
92
+
93
+ container.modem.demuxStream(stream, stdoutStream, stderrStream);
94
+ }
95
+
96
+ await docker.getNetwork(network).connect({
97
+ Container: container.id,
98
+ EndpointConfig: {
99
+ Aliases: aliases
100
+ }
101
+ });
102
+
103
+ return container;
104
+ }
105
+
106
+ /**
107
+ * Create network
108
+ *
109
+ * @param {String} name - Network name
110
+ */
111
+ export async function createBridgeNetwork({ name }) {
112
+ try {
113
+ await docker.createNetwork({
114
+ Name: name,
115
+ Driver: 'bridge',
116
+ CheckDuplicate: true
117
+ });
118
+ } catch (err) {
119
+ if (err.statusCode === 409) {
120
+ // network already exists, ignore
121
+ return;
122
+ }
123
+ console.error(err);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Tansform stream
129
+ *
130
+ * @param {String} name - Container name
131
+ * @returns {Transform} - Transform stream
132
+ */
133
+ function createPrefixStream(name, targetStream) {
134
+ const color = colorForName(name);
135
+ const labelWidth = 38;
136
+ name = '[' + name + ']';
137
+ const paddedName = name.padEnd(labelWidth, ' ');
138
+
139
+ return new Writable({
140
+ write(chunk, encoding, callback) {
141
+ const lines = chunk.toString().split(/\r?\n/);
142
+ for (const line of lines) {
143
+ if (line.trim()) {
144
+ targetStream.write(`${color}${paddedName}${RESET} ${line}\n`);
145
+ }
146
+ }
147
+ callback();
148
+ }
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Colours for container name
154
+ */
155
+ const COLORS = [
156
+ '\x1b[31m', // red
157
+ '\x1b[32m', // green
158
+ '\x1b[33m', // yellow
159
+ '\x1b[34m', // blue
160
+ '\x1b[35m', // magenta
161
+ '\x1b[36m', // cyan
162
+ '\x1b[37m', // white
163
+ ];
164
+
165
+ const RESET = '\x1b[0m';
166
+
167
+ function colorForName(name) {
168
+ let hash = 0;
169
+ for (let i = 0; i < name.length; i++) {
170
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
171
+ }
172
+ return COLORS[Math.abs(hash) % COLORS.length];
173
+ }
@@ -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);