@gnar-engine/cli 1.0.0 → 1.0.1

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 (230) hide show
  1. package/assets/gnar-engine-logo-white.svg +9 -0
  2. package/bootstrap/deploy.localdev.yml +51 -0
  3. package/bootstrap/secrets.localdev.yml +27 -0
  4. package/bootstrap/services/agent/Dockerfile +23 -0
  5. package/bootstrap/services/agent/notes.md +28 -0
  6. package/bootstrap/services/agent/package.json +16 -0
  7. package/bootstrap/services/agent/src/app.js +52 -0
  8. package/bootstrap/services/agent/src/commands/agent.handler.js +104 -0
  9. package/bootstrap/services/agent/src/config.js +52 -0
  10. package/bootstrap/services/agent/src/controllers/http.controller.js +44 -0
  11. package/bootstrap/services/agent/src/controllers/message.controller.js +51 -0
  12. package/bootstrap/services/agent/src/db/migrations/01-init.js +50 -0
  13. package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +36 -0
  14. package/bootstrap/services/agent/src/policies/agent.policy.js +13 -0
  15. package/bootstrap/services/agent/src/schema/Agent.schema.js +17 -0
  16. package/bootstrap/services/agent/src/services/agent.service.js +259 -0
  17. package/bootstrap/services/agent/src/services/chatgpt.service.js +46 -0
  18. package/bootstrap/services/agent/src/services/manifest.service.js +21 -0
  19. package/bootstrap/services/control/Dockerfile +23 -0
  20. package/bootstrap/services/control/Dockerfile.prod +37 -0
  21. package/bootstrap/services/control/README.md +25 -0
  22. package/bootstrap/services/control/package.json +16 -0
  23. package/bootstrap/services/control/src/app.js +45 -0
  24. package/bootstrap/services/control/src/commands/control.handler.js +231 -0
  25. package/bootstrap/services/control/src/commands/service.handler.js +81 -0
  26. package/bootstrap/services/control/src/commands/task.handler.js +247 -0
  27. package/bootstrap/services/control/src/config.js +55 -0
  28. package/bootstrap/services/control/src/controllers/http.controller.js +228 -0
  29. package/bootstrap/services/control/src/controllers/message.controller.js +40 -0
  30. package/bootstrap/services/control/src/db/migrations/01-init.js +50 -0
  31. package/bootstrap/services/control/src/db/migrations/02-control-service-init.js +60 -0
  32. package/bootstrap/services/control/src/db/migrations/03-alter-tasks.js +29 -0
  33. package/bootstrap/services/control/src/policies/task.policy.js +53 -0
  34. package/bootstrap/services/control/src/schema/control.schema.js +42 -0
  35. package/bootstrap/services/control/src/services/registry.service.js +83 -0
  36. package/bootstrap/services/control/src/services/reset.service.js +28 -0
  37. package/bootstrap/services/control/src/services/task.service.js +153 -0
  38. package/bootstrap/services/control/src/tests/control.test.js +50 -0
  39. package/bootstrap/services/notification/Dockerfile +23 -0
  40. package/bootstrap/services/notification/Dockerfile.prod +37 -0
  41. package/bootstrap/services/notification/README.md +3 -0
  42. package/bootstrap/services/notification/package.json +34 -0
  43. package/bootstrap/services/notification/src/app.js +51 -0
  44. package/bootstrap/services/notification/src/commands/command-bus.js +20 -0
  45. package/bootstrap/services/notification/src/commands/handlers/control.handler.js +18 -0
  46. package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +157 -0
  47. package/bootstrap/services/notification/src/config.js +15 -0
  48. package/bootstrap/services/notification/src/controllers/message.controller.js +82 -0
  49. package/bootstrap/services/notification/src/services/logger.service.js +16 -0
  50. package/bootstrap/services/notification/src/services/ses.service.js +23 -0
  51. package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +136 -0
  52. package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +87 -0
  53. package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +132 -0
  54. package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +77 -0
  55. package/bootstrap/services/notification/src/tests/notification.test.js +0 -0
  56. package/bootstrap/services/portal/Dockerfile +23 -0
  57. package/bootstrap/services/portal/Dockerfile.remote +40 -0
  58. package/bootstrap/services/portal/README.md +22 -0
  59. package/bootstrap/services/portal/nginx.conf +12 -0
  60. package/bootstrap/services/portal/package.json +59 -0
  61. package/bootstrap/services/portal/public/favicon.ico +0 -0
  62. package/bootstrap/services/portal/public/gnar-white.png +0 -0
  63. package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
  64. package/bootstrap/services/portal/public/index.html +43 -0
  65. package/bootstrap/services/portal/public/logo192.png +0 -0
  66. package/bootstrap/services/portal/public/logo512.png +0 -0
  67. package/bootstrap/services/portal/public/manifest.json +25 -0
  68. package/bootstrap/services/portal/public/robots.txt +3 -0
  69. package/bootstrap/services/portal/src/App.js +56 -0
  70. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +1 -0
  71. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +1 -0
  72. package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +1 -0
  73. package/bootstrap/services/portal/src/assets/activity.svg +3 -0
  74. package/bootstrap/services/portal/src/assets/arrow.svg +3 -0
  75. package/bootstrap/services/portal/src/assets/bin-white.svg +3 -0
  76. package/bootstrap/services/portal/src/assets/bin.svg +3 -0
  77. package/bootstrap/services/portal/src/assets/check.svg +3 -0
  78. package/bootstrap/services/portal/src/assets/chevron.svg +3 -0
  79. package/bootstrap/services/portal/src/assets/contact.svg +3 -0
  80. package/bootstrap/services/portal/src/assets/dots-vertical.svg +5 -0
  81. package/bootstrap/services/portal/src/assets/eye-off.svg +3 -0
  82. package/bootstrap/services/portal/src/assets/eye.svg +4 -0
  83. package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +47 -0
  84. package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +47 -0
  85. package/bootstrap/services/portal/src/assets/gnar_engine.svg +3 -0
  86. package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
  87. package/bootstrap/services/portal/src/assets/home.svg +3 -0
  88. package/bootstrap/services/portal/src/assets/link.svg +3 -0
  89. package/bootstrap/services/portal/src/assets/lock.svg +3 -0
  90. package/bootstrap/services/portal/src/assets/package.svg +4 -0
  91. package/bootstrap/services/portal/src/assets/raffle.svg +3 -0
  92. package/bootstrap/services/portal/src/assets/settings.svg +4 -0
  93. package/bootstrap/services/portal/src/assets/shopping-bag.svg +3 -0
  94. package/bootstrap/services/portal/src/assets/user-black.svg +3 -0
  95. package/bootstrap/services/portal/src/assets/user.svg +3 -0
  96. package/bootstrap/services/portal/src/assets/users.svg +3 -0
  97. package/bootstrap/services/portal/src/assets/wallet.svg +3 -0
  98. package/bootstrap/services/portal/src/css/style.css +1007 -0
  99. package/bootstrap/services/portal/src/data/data.js +70 -0
  100. package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +32 -0
  101. package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +160 -0
  102. package/bootstrap/services/portal/src/features/crud/crudEdit.less +230 -0
  103. package/bootstrap/services/portal/src/features/crud/crudList.less +134 -0
  104. package/bootstrap/services/portal/src/features/crud/crudPage.less +31 -0
  105. package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +108 -0
  106. package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +243 -0
  107. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +109 -0
  108. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +315 -0
  109. package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +104 -0
  110. package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +388 -0
  111. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +104 -0
  112. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +208 -0
  113. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +110 -0
  114. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +261 -0
  115. package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +107 -0
  116. package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +402 -0
  117. package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +30 -0
  118. package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +113 -0
  119. package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +56 -0
  120. package/bootstrap/services/portal/src/features/loginForm/loginForm.less +56 -0
  121. package/bootstrap/services/portal/src/features/notes/Notes.jsx +18 -0
  122. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +96 -0
  123. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +74 -0
  124. package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +102 -0
  125. package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +24 -0
  126. package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +99 -0
  127. package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +46 -0
  128. package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +64 -0
  129. package/bootstrap/services/portal/src/features/sidebar/sidebar.less +49 -0
  130. package/bootstrap/services/portal/src/features/skus/Skus.jsx +109 -0
  131. package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +44 -0
  132. package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +32 -0
  133. package/bootstrap/services/portal/src/features/user/User.jsx +54 -0
  134. package/bootstrap/services/portal/src/features/user/user.less +57 -0
  135. package/bootstrap/services/portal/src/includes/utilities.js +259 -0
  136. package/bootstrap/services/portal/src/index.js +14 -0
  137. package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +50 -0
  138. package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +17 -0
  139. package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +48 -0
  140. package/bootstrap/services/portal/src/layouts/loginLayout.less +33 -0
  141. package/bootstrap/services/portal/src/layouts/portalLayout.less +67 -0
  142. package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +199 -0
  143. package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +17 -0
  144. package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +10 -0
  145. package/bootstrap/services/portal/src/pages/login/Login.jsx +15 -0
  146. package/bootstrap/services/portal/src/pages/login/login.less +10 -0
  147. package/bootstrap/services/portal/src/pages/orders/Orders.jsx +199 -0
  148. package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +15 -0
  149. package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +15 -0
  150. package/bootstrap/services/portal/src/pages/payments/Payments.jsx +10 -0
  151. package/bootstrap/services/portal/src/pages/portal/Portal.jsx +43 -0
  152. package/bootstrap/services/portal/src/pages/products/Products.jsx +212 -0
  153. package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +124 -0
  154. package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +186 -0
  155. package/bootstrap/services/portal/src/pages/reports/Reports.jsx +10 -0
  156. package/bootstrap/services/portal/src/pages/settings/Settings.jsx +10 -0
  157. package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +199 -0
  158. package/bootstrap/services/portal/src/pages/users/Users.jsx +193 -0
  159. package/bootstrap/services/portal/src/pages/users/users.less +25 -0
  160. package/bootstrap/services/portal/src/slices/authSlice.js +71 -0
  161. package/bootstrap/services/portal/src/store/configureStore.js +12 -0
  162. package/bootstrap/services/portal/src/styles/global.less +159 -0
  163. package/bootstrap/services/portal/src/styles/inputs.less +157 -0
  164. package/bootstrap/services/portal/src/styles/main.less +26 -0
  165. package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +97 -0
  166. package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +23 -0
  167. package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +17 -0
  168. package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +42 -0
  169. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +63 -0
  170. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +63 -0
  171. package/bootstrap/services/portal/src/ui/customSelect/CustomSelect.jsx +63 -0
  172. package/bootstrap/services/portal/src/ui/customSelect/customSelect.less +92 -0
  173. package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +19 -0
  174. package/bootstrap/services/portal/src/ui/loader/Loader.jsx +12 -0
  175. package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +23 -0
  176. package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +29 -0
  177. package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +69 -0
  178. package/bootstrap/services/portal/src/ui/saveButton/saveButton.less +0 -0
  179. package/bootstrap/services/rabbit-mq/Dockerfile.prod +9 -0
  180. package/bootstrap/services/user/Dockerfile +23 -0
  181. package/bootstrap/services/user/Dockerfile.prod +37 -0
  182. package/bootstrap/services/user/README.md +8 -0
  183. package/bootstrap/services/user/package.json +16 -0
  184. package/bootstrap/services/user/src/app.js +45 -0
  185. package/bootstrap/services/user/src/commands/session.handler.js +23 -0
  186. package/bootstrap/services/user/src/commands/user.handler.js +286 -0
  187. package/bootstrap/services/user/src/config.js +73 -0
  188. package/bootstrap/services/user/src/controllers/http.controller.js +156 -0
  189. package/bootstrap/services/user/src/controllers/message.controller.js +51 -0
  190. package/bootstrap/services/user/src/db/migrations/01-init.js +50 -0
  191. package/bootstrap/services/user/src/db/migrations/02-user-service-init.js +63 -0
  192. package/bootstrap/services/user/src/db/migrations/03-unauth-sessions.js +43 -0
  193. package/bootstrap/services/user/src/db/seeders/development/01-root-user.js +29 -0
  194. package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +27 -0
  195. package/bootstrap/services/user/src/db/seeders/production/01-root-user.js +29 -0
  196. package/bootstrap/services/user/src/policies/user.policy.js +81 -0
  197. package/bootstrap/services/user/src/schema/user.schema.js +69 -0
  198. package/bootstrap/services/user/src/services/authentication.service.js +127 -0
  199. package/bootstrap/services/user/src/services/session.service.js +58 -0
  200. package/bootstrap/services/user/src/services/user.service.js +130 -0
  201. package/bootstrap/services/user/src/tests/user.test.js +126 -0
  202. package/package.json +3 -7
  203. package/src/agent/agent.client.js +28 -0
  204. package/src/agent/commands.js +48 -0
  205. package/src/cli.js +30 -0
  206. package/src/config.js +8 -0
  207. package/src/control/commands.js +156 -0
  208. package/src/control/control.client.js +127 -0
  209. package/src/dev/commands.js +71 -0
  210. package/src/dev/dev.service.js +320 -0
  211. package/src/engine/infra.js +142 -0
  212. package/src/helpers/helpers.js +63 -0
  213. package/src/profiles/command.js +170 -0
  214. package/src/profiles/profiles.client.js +101 -0
  215. package/src/scaffolder/commands.js +123 -0
  216. package/src/scaffolder/scaffolder.handler.js +252 -0
  217. package/src/services/client.js +174 -0
  218. package/templates/service/Dockerfile.hbs +20 -0
  219. package/templates/service/app.js.hbs +38 -0
  220. package/templates/service/commands/{{serviceName}}.handler.js.hbs +97 -0
  221. package/templates/service/config.js.hbs +48 -0
  222. package/templates/service/controllers/http.controller.js.hbs +87 -0
  223. package/templates/service/controllers/message.controller.js.hbs +51 -0
  224. package/templates/service/db/migrations/01-init.js.hbs +50 -0
  225. package/templates/service/db/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +23 -0
  226. package/templates/service/package.json.hbs +18 -0
  227. package/templates/service/policies/{{serviceName}}.policy.js.hbs +49 -0
  228. package/templates/service/schema/{{serviceName}}.schema.js.hbs +14 -0
  229. package/templates/service/services/{{serviceName}}.service.js.hbs +32 -0
  230. package/dist/cli.js +0 -18
@@ -0,0 +1,252 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import yaml from 'js-yaml';
4
+ import { profiles } from '../profiles/profiles.client.js';
5
+ import { helpers } from '../helpers/helpers.js';
6
+ import Handlebars from 'handlebars';
7
+
8
+ /**
9
+ * Gnar Engine Scaffolder
10
+ */
11
+ export const scaffolder = {
12
+
13
+ /**
14
+ * Create a new service
15
+ *
16
+ * @param {object} param
17
+ * @param {string} param.serviceName - The name of the service to create
18
+ * @param {string} param.database - The database type (e.g., 'mysql', 'mongodb')
19
+ * @param {string} param.projectDir - The project directory where the service will be created
20
+ * @returns {object} - An object containing a success message and the service path
21
+ */
22
+ createNewService: function ({serviceName, database, projectDir}) {
23
+ const serviceDir = projectDir + 'services/' + serviceName;
24
+ console.log('Service directory:', serviceDir);
25
+
26
+ // Check if the service directory already exists
27
+ if (fs.existsSync(serviceDir)) {
28
+ throw new Error(`Service "${serviceName}" already exists at ${serviceDir}`);
29
+ }
30
+
31
+ // Create the service directory
32
+ fs.mkdirSync(serviceDir, { recursive: true });
33
+
34
+ // Get all files in the templates directory
35
+ const templatesDir = path.join(import.meta.dirname, '../../templates/service');
36
+ const templateFiles = scaffolder.getAllTemplateFiles({
37
+ dir: templatesDir,
38
+ baseDir: templatesDir
39
+ });
40
+
41
+ console.log('Template files:', templateFiles);
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
+ switch (file.extension) {
58
+ case '.hbs':
59
+ // Compile the Handlebars template for content
60
+ const templateContent = fs.readFileSync(file.fullPath, 'utf8');
61
+ const compiledTemplate = Handlebars.compile(templateContent);
62
+ const renderedContent = compiledTemplate(templateArgs);
63
+
64
+ // Compile the Handlebars template for the filename (excluding .hbs)
65
+ const filenameTemplate = Handlebars.compile(file.relativePath.replace(/\.hbs$/, ''));
66
+ const renderedFilename = filenameTemplate(templateArgs);
67
+ targetPath = path.join(serviceDir, renderedFilename);
68
+
69
+ // Ensure directory exists
70
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
71
+ fs.writeFileSync(targetPath, renderedContent, 'utf8');
72
+ break;
73
+ default:
74
+ // By default, copy the file to the service directory
75
+ sourcePath = file.fullPath;
76
+ targetPath = path.join(serviceDir, file.relativePath);
77
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
78
+ fs.copyFileSync(sourcePath, targetPath);
79
+ break;
80
+ }
81
+ });
82
+
83
+ return {
84
+ message: `Service "${serviceName}" created successfully at ${serviceDir}`,
85
+ servicePath: serviceDir
86
+ };
87
+ },
88
+
89
+ /**
90
+ * Create new front-end service
91
+ *
92
+ * @param {object} param
93
+ * @param {string} param.serviceName - The name of the service to create
94
+ * @param {string} param.projectDir - The project directory where the service will be created
95
+ * @returns {object} - An object containing a success message and the service path
96
+ */
97
+ createNewFrontEndService: function ({serviceName, projectDir}) {
98
+ const serviceDir = projectDir + 'services/' + serviceName;
99
+ console.log('Service directory:', serviceDir);
100
+
101
+ // Check if the service directory already exists
102
+ if (fs.existsSync(serviceDir)) {
103
+ throw new Error(`Service "${serviceName}" already exists at ${serviceDir}`);
104
+ }
105
+
106
+ // Create the service directory
107
+ fs.mkdirSync(serviceDir, { recursive: true });
108
+ },
109
+
110
+ /**
111
+ * Recursively get all template files in a directory
112
+ *
113
+ * @param {object} params
114
+ * @param {string} params.dir - The directory to search for template files
115
+ * @param {string} [params.baseDir=dir] - The base directory for relative paths
116
+ * @param {Array} [params.fileList=[]] - The list to store found files
117
+ * @returns {Array} - An array of objects containing full and relative paths of template files
118
+ */
119
+ getAllTemplateFiles: function ({dir, baseDir = dir, fileList = []}) {
120
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
121
+
122
+ entries.forEach(entry => {
123
+ const fullPath = path.join(dir, entry.name);
124
+ if (entry.isDirectory()) {
125
+ if (entry.name !== 'node_modules' && entry.name !== 'data') {
126
+ if (!entry.name.startsWith('.') || entry.name == '.gnarengine') {
127
+ scaffolder.getAllTemplateFiles({
128
+ dir: fullPath,
129
+ baseDir,
130
+ fileList
131
+ });
132
+ }
133
+ }
134
+ } else {
135
+ const relativePath = path.relative(baseDir, fullPath);
136
+
137
+ if (entry.name !== 'docker-compose.dev.yml') {
138
+ fileList.push({ fullPath, relativePath, extension: path.extname(entry.name) });
139
+ }
140
+ }
141
+ });
142
+
143
+ return fileList;
144
+ },
145
+
146
+ /**
147
+ * Create a new project
148
+ *
149
+ * @param {object} param
150
+ * @param {string} param.projectName - The name of the project to create
151
+ * @param {string} param.projectDir - The directory where the project will be created
152
+ * @param {string} param.rootAdminEmail - The email for the root admin user
153
+ */
154
+ createNewProject: function ({projectName, projectDir, rootAdminEmail}) {
155
+ projectName = projectName.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9\-]/g, '');
156
+
157
+ const fullProjectPath = path.join(projectDir, projectName);
158
+ console.log('Creating project in:', fullProjectPath);
159
+
160
+ // Check to make sure there isn't already a profile with this project name
161
+ const profileName = projectName + ':local';
162
+ const allProfiles = profiles.getAllProfiles();
163
+
164
+ if (allProfiles && allProfiles.profiles) {
165
+ if (allProfiles.profiles[profileName]) {
166
+ throw new Error(`A profile with the name "${projectName}:local" already exists. Please choose a different project name or delete the existing profile.`);
167
+ }
168
+ }
169
+
170
+ // Create the project directory
171
+ if (!fs.existsSync(fullProjectPath)) {
172
+ fs.mkdirSync(fullProjectPath, { recursive: true });
173
+ fs.mkdirSync(path.join(fullProjectPath, 'data'), { recursive: true });
174
+ }
175
+
176
+ // Copy bootstrap
177
+ const bootstrapDir = path.join(import.meta.dirname, '../../bootstrap');
178
+ const bootstrapFiles = scaffolder.getAllTemplateFiles({
179
+ dir: path.join(bootstrapDir),
180
+ baseDir: path.join(bootstrapDir)
181
+ });
182
+
183
+ let cliApiKey = '';
184
+
185
+ console.log('Copying bootstrap');
186
+
187
+ if (!bootstrapFiles || bootstrapFiles.length === 0) {
188
+ throw new Error('No bootstrap files found');
189
+ }
190
+
191
+ bootstrapFiles.forEach(file => {
192
+ let sourcePath;
193
+ let targetPath;
194
+
195
+ sourcePath = file.fullPath;
196
+ targetPath = path.join(fullProjectPath, file.relativePath);
197
+
198
+ // create random secrets
199
+ if (file.relativePath === 'secrets.localdev.yml') {
200
+ console.log('Creating random secrets: secrets.localdev.yml');
201
+ const secretsPath = path.join(bootstrapDir, 'secrets.localdev.yml');
202
+ const rawSecrets = fs.readFileSync(secretsPath, 'utf8');
203
+ const parsedSecrets = yaml.load(rawSecrets);
204
+
205
+ // generate random passwords
206
+ Object.keys(parsedSecrets.services).forEach(serviceName => {
207
+ Object.keys(parsedSecrets.services[serviceName]).forEach(key => {
208
+ if (key.toLowerCase().includes('pass')) {
209
+ parsedSecrets.services[serviceName][key] = helpers.generateRandomString(16);
210
+ }
211
+ });
212
+ });
213
+
214
+ // set random root api key
215
+ cliApiKey = helpers.generateRandomString(32);
216
+ parsedSecrets.services.user.ROOT_ADMIN_API_KEY = cliApiKey;
217
+ parsedSecrets.services.user.ROOT_ADMIN_EMAIL = rootAdminEmail || 'admin@' + projectName + '.local';
218
+
219
+ // save updated secrets file
220
+ const newSecretsContent = yaml.dump(parsedSecrets);
221
+ fs.writeFileSync(secretsPath, newSecretsContent, 'utf8');
222
+ }
223
+
224
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
225
+ fs.copyFileSync(sourcePath, targetPath);
226
+ });
227
+
228
+ // Create the profile
229
+ console.log('Creating new CLI profile: ' + profileName);
230
+ const config = {
231
+ CLI_API_URL: 'http://localhost',
232
+ CLI_API_USERNAME: 'gnarlyroot',
233
+ CLI_API_KEY: cliApiKey,
234
+ PROJECT_DIR: fullProjectPath
235
+ };
236
+
237
+ // save profile to config file
238
+ profiles.createProfile({
239
+ profileName: profileName,
240
+ config: config
241
+ });
242
+
243
+ // set active profile if required
244
+ console.log('Setting CLI profile to active');
245
+ profiles.setActiveProfile({
246
+ profileName: profileName
247
+ });
248
+
249
+ console.log('g n a r e n g i n e - Created new project: ' + projectName);
250
+ console.log('Run `gnar dev up` to start the development server');
251
+ }
252
+ }
@@ -0,0 +1,174 @@
1
+ import axios from 'axios';
2
+ import { profiles } from '../profiles/profiles.client.js';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import os from 'os';
6
+
7
+ let client;
8
+ let profile;
9
+ let profileName;
10
+ let baseApiUrl;
11
+ let authToken;
12
+ let username;
13
+ let apiKey;
14
+
15
+ /**
16
+ * Initialise the profile
17
+ */
18
+ export const initAxiosClient = () => {
19
+ // Determine the correct API URL based on the environment
20
+ try {
21
+ profile = profiles.getActiveProfile();
22
+ baseApiUrl = profile.profile.CLI_API_URL;
23
+ username = profile.profile.CLI_API_USERNAME;
24
+ apiKey = profile.profile.CLI_API_KEY;
25
+ profileName = profile.name;
26
+
27
+ console.log('Profile: ' + profile.name + ' | ' + baseApiUrl);
28
+
29
+ } catch (error) {
30
+ console.error('Error retrieving active profile:', error.message);
31
+ throw new Error('Active profile not found. Please set an active profile using `gnar profile set-active <profileName>`');
32
+ }
33
+
34
+ // Create axios client
35
+ client = axios.create({
36
+ baseURL: baseApiUrl,
37
+ withCredentials: true,
38
+ });
39
+
40
+ // Request interceptors
41
+ client.interceptors.request.use(
42
+ async (config) => {
43
+
44
+ // Return pre-existing acces token if we have one
45
+ try {
46
+ authToken = await getAccessToken({activeProfileName: profileName});
47
+
48
+ if (authToken) {
49
+ config.headers['Authorization'] = `Bearer ${authToken}`;
50
+ }
51
+
52
+ return config;
53
+ } catch (error) {
54
+ //console.log('No access token found, authenticating...');
55
+ }
56
+
57
+ // Authenticate if no token is found
58
+ try {
59
+ const authResponse = await axios.post(`${baseApiUrl}/authenticate/`, {
60
+ username: username,
61
+ apiKey: apiKey
62
+ });
63
+
64
+ if (!authResponse.data?.token) {
65
+ throw new Error('Authentication failed', authResponse.data?.error || 'No token received');
66
+ }
67
+
68
+ authToken = authResponse.data.token;
69
+
70
+ setAccessToken({
71
+ activeProfileName: profileName,
72
+ accessToken: authToken,
73
+ accessTokenExpires: new Date(Date.now() + 3600000).toISOString()
74
+ });
75
+
76
+ if (authToken) {
77
+ config.headers['Authorization'] = `Bearer ${authToken}`;
78
+ }
79
+
80
+ return config;
81
+ } catch (error) {
82
+ console.error('Authentication failed:', error.response?.data?.message || error.message);
83
+ throw error;
84
+ }
85
+ },
86
+ (error) => {
87
+ return Promise.reject(error);
88
+ }
89
+ );
90
+ }
91
+
92
+
93
+ export default client;
94
+
95
+
96
+ /**
97
+ * Get data resource
98
+ */
99
+ export const getDataResource = async (resourceSlug) => {
100
+ try {
101
+ const response = await client.get(`/data/${resourceSlug}`);
102
+ return response.data;
103
+ } catch (error) {
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get access token
110
+ *
111
+ * @param {Object} options - Options for getting the access token
112
+ * @param {string} options.activeProfileName - The name of the active profile
113
+ * @returns {Promise<string>} - The access token
114
+ * @throws {Error} - If the access token is not found or has expired
115
+ */
116
+ export const getAccessToken = async ({activeProfileName}) => {
117
+
118
+ let accessToken = null;
119
+ let accessTokenExpires = null;
120
+
121
+ try {
122
+ const profileCachePath = path.join(os.homedir(), '.gnarengine', activeProfileName + '.json');
123
+ const profileCache = JSON.parse(fs.readFileSync(profileCachePath, 'utf-8'));
124
+ accessToken = profileCache.accessToken;
125
+ accessTokenExpires = profileCache.accessTokenExpires;
126
+
127
+ if (!accessToken || !accessTokenExpires) {
128
+ throw new Error('Access token or expiration not found in cache');
129
+ }
130
+
131
+ if (new Date(accessTokenExpires) < new Date()) {
132
+ throw new Error('Access token has expired');
133
+ }
134
+ } catch (error) {
135
+ throw error;
136
+ }
137
+
138
+ return accessToken;
139
+ }
140
+
141
+ /**
142
+ * Set access token
143
+ *
144
+ * @param {Object} options - Options for setting the access token
145
+ * @param {string} options.activeProfileName - The name of the active profile
146
+ * @param {string} options.accessToken - The access token to set
147
+ * @param {Date} options.accessTokenExpires - The expiration date of the access token
148
+ * @returns {Promise<void>}
149
+ * @throws {Error} - If unable to write to the profile cache file
150
+ */
151
+ export const setAccessToken = async ({activeProfileName, accessToken, accessTokenExpires}) => {
152
+ try {
153
+ let profileCache = {};
154
+
155
+ const profileCachePath = path.join(os.homedir(), '.gnarengine', activeProfileName + '.json');
156
+
157
+ if (fs.existsSync(profileCachePath)) {
158
+ try {
159
+ profileCache = JSON.parse(fs.readFileSync(profileCachePath, 'utf-8'));
160
+ } catch (error) {
161
+
162
+ }
163
+ } else {
164
+ fs.mkdirSync(path.dirname(profileCachePath), { recursive: true });
165
+ }
166
+
167
+ profileCache.accessToken = accessToken;
168
+ profileCache.accessTokenExpires = accessTokenExpires;
169
+
170
+ fs.writeFileSync(profileCachePath, JSON.stringify(profileCache, null, 2));
171
+ } catch (error) {
172
+ throw new Error('Unable to write to profile cache file: ' + error.message);
173
+ }
174
+ }
@@ -0,0 +1,20 @@
1
+ # Dockerfile for {{pascalCase serviceName}} Service
2
+ FROM gnarengine/service-core:1.0.0
3
+
4
+ # Set the working directory
5
+ WORKDIR /usr/gnar_engine/app
6
+
7
+ # Copy package.json and package-lock.json
8
+ COPY ./services/{{serviceName}}/package*.json ./
9
+
10
+ # Install nodemon
11
+ RUN npm install -g nodemon
12
+
13
+ # Install app dependencies
14
+ RUN npm install
15
+
16
+ # Expose the port the service will run on
17
+ EXPOSE 3000
18
+
19
+ # Start the application
20
+ CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
@@ -0,0 +1,38 @@
1
+ import { message, http, logger, db } from '@gnar-engine/core';
2
+ import { config } from './config.js';
3
+ import { messageHandlers } from './controllers/message.controller.js';
4
+ import { httpController as {{pascalCase serviceName}}PlatformHttpController } from './controllers/http.controller.js';
5
+
6
+ /**
7
+ * Initialise service
8
+ */
9
+ export const initService = async () => {
10
+
11
+ // Run migrations
12
+ db.migrations.runMigrations({config});
13
+ db.seeders.runSeeders({config});
14
+
15
+ // Import command handlers after the command bus is initialised
16
+ await import('./commands/{{lowerCasePlural serviceName}}.handler.js');
17
+ // Add more handlers as needed
18
+
19
+ // Initialise and register message handlers
20
+ await message.init({
21
+ config: config.message,
22
+ handlers: messageHandlers
23
+ });
24
+
25
+ // Register http routes
26
+ await http.registerRoutes({
27
+ controllers: [
28
+ {{pascalCase serviceName}}PlatformHttpController,
29
+ ]
30
+ });
31
+
32
+ // Start the HTTP server
33
+ await http.start();
34
+
35
+ logger.info('G n a r E n g i n e | {{capitaliseFirstLetter serviceName}} Service initialised successfully.');
36
+ }
37
+
38
+ initService();
@@ -0,0 +1,97 @@
1
+ import { commands, logger, error } from '@gnar-engine/core';
2
+ import { auth } from '../services/authentication.service.js';
3
+ import { {{serviceName}} } from '../services/{{serviceName}}.service.js';
4
+ import { config } from '../config.js';
5
+ import { validate{{pascalCase serviceName}}, validateServiceAdmin{{pascalCase serviceName}}, validate{{pascalCase serviceName}}Update, validateServiceAdmin{{pascalCase serviceName}}Update } from '../schema/{{serviceName}}.schema.js';
6
+
7
+
8
+ /**
9
+ * Get single {{serviceName}}
10
+ */
11
+ commands.register('{{serviceName}}Service.getSingle{{pascalCase serviceName}}', async ({id}) => {
12
+ if (id) {
13
+ return await {{serviceName}}.getById({id: id});
14
+ } else {
15
+ throw new error.badRequest('{{pascalCase serviceName}} email or id required');
16
+ }
17
+ });
18
+
19
+ /**
20
+ * Get many {{lowerCasePlural serviceName}}
21
+ */
22
+ commands.register('{{serviceName}}Service.getMany{{pascalCasePlural serviceName}}', async ({}) => {
23
+ return await {{serviceName}}.getAll();
24
+ });
25
+
26
+ /**
27
+ * Create {{lowerCasePlural serviceName}}
28
+ */
29
+ commands.register('{{serviceName}}Service.create{{pascalCasePlural serviceName}}', async ({ {{lowerCasePlural serviceName}} }) => {
30
+ const validationErrors = [];
31
+ let createdNew{{pascalCasePlural serviceName}} = [];
32
+
33
+ for (const newData of {{lowerCasePlural serviceName}}) {
34
+ const { errors } = validate{{pascalCase serviceName}}(newData);
35
+ if (errors?.length) {
36
+ validationErrors.push(errors);
37
+ continue;
38
+ }
39
+
40
+ const created = await {{serviceName}}.create(newData);
41
+ createdNew{{pascalCasePlural serviceName}}.push(created);
42
+ }
43
+
44
+ if (validationErrors.length) {
45
+ throw new error.badRequest(`Invalid {{serviceName}} data: ${validationErrors}`);
46
+ }
47
+
48
+ return createdNew{{pascalCasePlural serviceName}};
49
+ });
50
+
51
+ /**
52
+ * Update {{serviceName}}
53
+ */
54
+ commands.register('{{serviceName}}Service.update{{pascalCase serviceName}}', async ({id, new{{pascalCase serviceName}}Data}) => {
55
+
56
+ const validationErrors = [];
57
+
58
+ if (!id) {
59
+ throw new error.badRequest('{{pascalCase serviceName}} ID required');
60
+
61
+ }
62
+
63
+ const obj = await {{serviceName}}.getById({id: id});
64
+
65
+ if (!obj) {
66
+ throw new error.notFound('{{pascalCase serviceName}} not found');
67
+
68
+ }
69
+
70
+ delete new{{pascalCase serviceName}}Data.id;
71
+
72
+ const { errors } = validate{{pascalCase serviceName}}Update(new{{pascalCase serviceName}}Data);
73
+
74
+ if (errors?.length) {
75
+ validationErrors.push(errors);
76
+ }
77
+
78
+ if (validationErrors.length) {
79
+ throw new error.badRequest(`Invalid {{serviceName}} data: ${validationErrors}`);
80
+ }
81
+
82
+ return await {{serviceName}}.update({
83
+ id: id,
84
+ ...new{{pascalCase serviceName}}Data
85
+ });
86
+ });
87
+
88
+ /**
89
+ * Delete {{serviceName}}
90
+ */
91
+ commands.register('{{serviceName}}Service.delete{{pascalCase serviceName}}', async ({id}) => {
92
+ const obj = await {{serviceName}}.getById({id: id});
93
+ if (!obj) {
94
+ throw new error.notFound('{{pascalCase serviceName}} not found');
95
+ }
96
+ return await {{serviceName}}.delete({id: id});
97
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Gnar Engine Service Config
3
+ */
4
+ export const config = {
5
+ // service name
6
+ serviceName: '{{serviceName}}Service',
7
+
8
+ // microservice | modular-monolith
9
+ architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
10
+
11
+ // web server
12
+ http: {
13
+ allowedOrigins: [],
14
+ allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
15
+ allowedHeaders: ['Content-Type', 'Authorization'],
16
+ rateLimiting: {
17
+ max: 5,
18
+ timeWindow: '1 minute',
19
+ }
20
+ },
21
+
22
+ // database
23
+ db: {
24
+ // type: mongodb | mysql
25
+ type: 'mysql',
26
+
27
+ // MongoDB
28
+ connectionUrl: process.env.{{upperCase serviceName}}_MONGO_URL,
29
+ connectionArgs: {},
30
+
31
+ // MySQL
32
+ host: process.env.{{upperCase serviceName}}_MYSQL_HOST,
33
+ user: process.env.{{upperCase serviceName}}_MYSQL_USER,
34
+ password: process.env.{{upperCase serviceName}}_MYSQL_PASSWORD,
35
+ database: process.env.{{upperCase serviceName}}_MYSQL_DATABASE,
36
+ connectionLimit: 10,
37
+ queueLimit: 20,
38
+ maxRetries: 5
39
+ },
40
+
41
+ // message broker
42
+ message: {
43
+ queueName: '{{serviceName}}ServiceQueue',
44
+ prefetch: 20
45
+ },
46
+
47
+ hashNameSpace: '{{uuid}}',
48
+ }