@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,23 @@
1
+ import React from 'react';
2
+
3
+ const Pagination = ({ numPages, currentPage, setCurrentPage }) => {
4
+ const handlePageChange = (page) => {
5
+ setCurrentPage(page);
6
+ };
7
+
8
+ return (
9
+ <div className="pagination">
10
+ {Array.from({ length: numPages }, (_, i) => (
11
+ <button
12
+ key={i}
13
+ onClick={() => handlePageChange(i + 1)}
14
+ disabled={currentPage === i + 1}
15
+ >
16
+ {i + 1}
17
+ </button>
18
+ ))}
19
+ </div>
20
+ );
21
+ };
22
+
23
+ export default Pagination;
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+
3
+ const Repeater = ({ items = [], setItems, defaultItem, renderRow, buttonText }) => {
4
+
5
+ const addItem = () => {
6
+ setItems([...items, defaultItem]);
7
+ }
8
+
9
+ const updateItem = (index, newItem) => {
10
+ const newItems = [...items];
11
+ newItems[index] = newItem;
12
+ setItems(newItems);
13
+ };
14
+
15
+ const removeItem = (index) => setItems(items.filter((_, i) => i !== index));
16
+
17
+ return (
18
+ <div>
19
+ {items.length > 0 && items.map((item, index) =>
20
+ renderRow(item, index, (newItem) => updateItem(index, newItem), () => removeItem(index))
21
+ )}
22
+ <div className="button-cont right">
23
+ <button className="mainButton wide" onClick={addItem}>{buttonText}</button>
24
+ </div>
25
+ </div>
26
+ );
27
+ };
28
+
29
+ export default Repeater;
@@ -0,0 +1,69 @@
1
+ import React, { useState, useEffect } from 'react';
2
+
3
+ const SaveButton = ({ save, loading, textCreate, textCreateLoading, textCreateSuccess, textCreateError, textUpdate, textUpdateLoading, textUpdateSuccess, textUpdateError, isUpdating}) => {
4
+
5
+ const [buttonText, setButtonText] = useState(textCreate || 'Save');
6
+
7
+ // Determine the button class name based on the loading state
8
+ const getButtonClassName = () => {
9
+ if (loading === 'loading') {
10
+ return 'btn-loading';
11
+ }
12
+
13
+ if (loading === 'success') {
14
+ return 'success';
15
+ }
16
+
17
+ if (loading === 'error') {
18
+ return 'error';
19
+ }
20
+
21
+ if (isUpdating) {
22
+ return 'update';
23
+ } else {
24
+ return 'create';
25
+ }
26
+ };
27
+
28
+ useEffect(() => {
29
+ if (isUpdating) {
30
+ // If updating
31
+ switch (loading) {
32
+ case 'loading':
33
+ setButtonText(textUpdateLoading || 'Updating...');
34
+ break;
35
+ case 'success':
36
+ setButtonText(textUpdateSuccess || 'Updated');
37
+ break;
38
+ case 'error':
39
+ setButtonText(textUpdateError || 'Error');
40
+ break;
41
+ default:
42
+ setButtonText(textUpdate || 'Update');
43
+ break;
44
+ }
45
+ } else {
46
+ // If creating
47
+ switch (loading) {
48
+ case 'loading':
49
+ setButtonText(textCreateLoading || 'Creating...');
50
+ break;
51
+ case 'success':
52
+ setButtonText(textCreateSuccess || 'Saved');
53
+ break;
54
+ case 'error':
55
+ setButtonText(textCreateError || 'Error');
56
+ break;
57
+ default:
58
+ setButtonText(textCreate || 'Save');
59
+ break;
60
+ }
61
+ }
62
+ }, [loading, isUpdating, textCreate, textCreateLoading, textCreateSuccess, textCreateError, textUpdate, textUpdateLoading, textUpdateSuccess, textUpdateError]);
63
+
64
+ return (
65
+ <button className={getButtonClassName()} onClick={save}>{buttonText}</button>
66
+ );
67
+ };
68
+
69
+ export default SaveButton;
@@ -0,0 +1,9 @@
1
+ # From RabbitMQ official image
2
+ FROM rabbitmq:management
3
+
4
+ # # Copy shared Erlang cookie
5
+ # COPY .erlang.cookie /var/lib/rabbitmq/.erlang.cookie
6
+
7
+ # # Set correct permissions
8
+ # RUN chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie && \
9
+ # chmod 400 /var/lib/rabbitmq/.erlang.cookie
@@ -0,0 +1,23 @@
1
+ # Dockerfile for Product Service
2
+ FROM node:20-alpine
3
+
4
+ # Set the working directory
5
+ WORKDIR /usr/gnar_engine/app
6
+
7
+ # Define a global env var
8
+ ENV GLOBAL_SERVICE_BASE_DIR=/usr/gnar_engine/app/src/
9
+
10
+ # Copy package.json and package-lock.json
11
+ COPY ./services/user/package*.json ./
12
+
13
+ # Install nodemon
14
+ RUN npm install -g nodemon
15
+
16
+ # Install app dependencies
17
+ RUN npm install
18
+
19
+ # Expose the port the service will run on
20
+ EXPOSE 3000
21
+
22
+ # Start the application
23
+ CMD ["nodemon", "--watch", "./gnar_engine", "./gnar_engine/app.js"]
@@ -0,0 +1,37 @@
1
+ # Stage 1: Builder
2
+ FROM node:20-alpine AS builder
3
+
4
+ WORKDIR /app
5
+
6
+ # Copy app source
7
+ COPY ./services/user/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/user/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"]
@@ -0,0 +1,8 @@
1
+ # Gnar Engine Products Service
2
+
3
+ ### Commands
4
+
5
+ Start dev server npm start:dev
6
+ Start production server npm start
7
+ Run tests npm test
8
+ Seed production npm seed:production
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "gnar_engine_user",
3
+ "version": "1.0.0",
4
+ "description": "Gnar Engine - User Service",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node ./src/app.js",
8
+ "start:dev": "nodemon --watch ./src ./src/app.js",
9
+ "test": "jest --watchAll --verbose"
10
+ },
11
+ "author": "Gnar Software Ltd.",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "@gnar-engine/core": "^1.0.1"
15
+ }
16
+ }
@@ -0,0 +1,45 @@
1
+ import { message, http, logger, db, registerService, webSockets } from '@gnar-engine/core';
2
+ import { config } from './config.js';
3
+ import { messageHandlers } from './controllers/message.controller.js';
4
+ import { httpController as userPlatformHttpController } from './controllers/http.controller.js';
5
+
6
+
7
+ /**
8
+ * Initialise service
9
+ */
10
+ export const initService = async () => {
11
+
12
+ // Run migrations
13
+ await db.migrations.runMigrations({config});
14
+ db.seeders.runSeeders({config});
15
+
16
+ // Import command handlers after the command bus is initialised
17
+ await import('./commands/user.handler.js');
18
+ await import('./commands/session.handler.js');
19
+
20
+ // Initialise and register message handlers
21
+ await message.init({
22
+ config: config.message,
23
+ handlers: messageHandlers
24
+ });
25
+
26
+ // Initialise websocket client & server
27
+ await webSockets.init(config.webSockets, config.serviceName);
28
+
29
+ // Register http routes
30
+ await http.registerRoutes({
31
+ controllers: [
32
+ userPlatformHttpController,
33
+ ]
34
+ });
35
+
36
+ // Start the HTTP server
37
+ await http.start();
38
+
39
+ // Register service with control service
40
+ await registerService();
41
+
42
+ logger.info('G n a r E n g i n e | User Service initialised successfully.');
43
+ }
44
+
45
+ initService();
@@ -0,0 +1,23 @@
1
+ import { commands } from '@gnar-engine/core';
2
+ import { unauthenticatedSession } from '../services/session.service.js';
3
+
4
+
5
+ /**
6
+ * Create unauthenticated session token
7
+ *
8
+ * @returns {string} Session token
9
+ */
10
+ commands.register('userService.createUnauthenticatedSessionToken', async () => {
11
+ return unauthenticatedSession.createSessionToken();
12
+ });
13
+
14
+ /**
15
+ * Verify unauthenticated session token
16
+ *
17
+ * @param {Object} params
18
+ * @param {string} params.sessionToken
19
+ * @returns {boolean} Session token valid
20
+ */
21
+ commands.register('userService.verifyUnauthenticatedSessionToken', async ({sessionToken}) => {
22
+ return unauthenticatedSession.verifySessionToken({sessionToken});
23
+ });
@@ -0,0 +1,286 @@
1
+ import { commands, logger, error } from '@gnar-engine/core';
2
+ import { auth } from '../services/authentication.service.js';
3
+ import { user } from '../services/user.service.js';
4
+ import { config } from '../config.js';
5
+ import { validateUser, validateServiceAdminUser, validateUserUpdate, validateServiceAdminUserUpdate } from '../schema/user.schema.js';
6
+
7
+
8
+ /**
9
+ * Authentication
10
+ *
11
+ * @param {Object} params
12
+ * @param {string} params.email
13
+ * @param {string} params.password
14
+ * @param {string} params.apiKey
15
+ * @returns {Promise<Object>} The user data
16
+ */
17
+ commands.register('userService.authenticate', async ({username, password, apiKey}) => {
18
+
19
+ // authenticate
20
+ let token = '';
21
+
22
+ if (config.authenticationOptions.password_auth_enabled) {
23
+ if (username && password) {
24
+ // verify credentials
25
+ const userId = await auth.verifyCredentials({
26
+ username: username,
27
+ password: password
28
+ });
29
+
30
+ if (!userId) {
31
+ throw new error.unauthorised('Invalid credentials');
32
+ }
33
+
34
+ // create new session token
35
+ token = auth.createSessionToken(userId);
36
+ }
37
+ }
38
+
39
+ if (config.authenticationOptions.api_key_auth_enabled) {
40
+ if (apiKey && username) {
41
+ // verify credentials
42
+ const userId = await auth.verifyApiKey({
43
+ apiKey: apiKey,
44
+ username: username
45
+ });
46
+
47
+ if (!userId) {
48
+ throw new error.unauthorised('Invalid API key');
49
+ }
50
+
51
+ // create new session token
52
+ token = auth.createSessionToken(userId);
53
+ }
54
+ }
55
+
56
+ // send response
57
+ if (token) {
58
+ logger.info('returning token ' + token);
59
+ return token;
60
+ }
61
+
62
+ logger.info('invalid credentials');
63
+
64
+ throw new error.unauthorised('Invalid credentials');
65
+ });
66
+
67
+ /**
68
+ * Get authenticated user
69
+ *
70
+ * @param {Object} params
71
+ * @param {string} params.token - Session token
72
+ * @returns {Promise<Object>} The user data
73
+ */
74
+ commands.register('userService.getAuthenticatedUser', async ({token}) => {
75
+
76
+ const user_id = await auth.getAuthenticatedUser(token);
77
+
78
+ if (user_id) {
79
+ logger.info('Auth user: ' + user_id);
80
+ const userObj = await user.getById({id: user_id});
81
+
82
+ if (userObj) {
83
+ delete userObj.password;
84
+ delete userObj.apiKey;
85
+
86
+ return userObj;
87
+ }
88
+ }
89
+
90
+ return;
91
+ });
92
+
93
+
94
+ /**
95
+ * Get single user
96
+ *
97
+ * @param {Object} params
98
+ * @param {string|number} params.id - User ID
99
+ * @returns {Promise<Object>} The user data
100
+ */
101
+ commands.register('userService.getSingleUser', async ({id, email}) => {
102
+
103
+ if (id) {
104
+ return await user.getById({id: id});
105
+ } else if (email) {
106
+ return await user.getByEmail({email: email});
107
+ } else {
108
+ throw new error.badRequest('User email or id required');
109
+ }
110
+ });
111
+
112
+ /**
113
+ * Get many users
114
+ *
115
+ * @param {Object} params
116
+ * @returns {Promise<Object>} The user data
117
+ */
118
+ commands.register('userService.getManyUsers', async ({}) => {
119
+
120
+ return await user.getAll();
121
+ });
122
+
123
+ /**
124
+ * Creat users with random password
125
+ *
126
+ * @param {Object} params
127
+ * @param {Object} params.user - New user data
128
+ */
129
+ commands.register('userService.createUserWithRandomPassword', async ({user}) => {
130
+
131
+ // create random password
132
+ const password = Math.random().toString(36);
133
+ const userData = {
134
+ ...user,
135
+ password: password
136
+ };
137
+
138
+ logger.info('creating new user : ' + JSON.stringify(userData));
139
+
140
+ // create user
141
+ try {
142
+ const newUsers = await createUsers({users: [userData]})
143
+
144
+ if (!newUsers || newUsers.length === 0) {
145
+ throw new error.badRequest('User creation failed');
146
+ }
147
+
148
+ return newUsers[0];
149
+ } catch (error) {
150
+ throw new error.badRequest('User creation failed: ' + error);
151
+ }
152
+ });
153
+
154
+ /**
155
+ * Create users
156
+ *
157
+ * @param {Object} params
158
+ * @param {Object[]} params.users - Array of new user data
159
+ * @returns {Promise<Array>} Array of new users
160
+ */
161
+ commands.register('userService.createUsers', async ({users}) => {
162
+
163
+ const validationErrors = [];
164
+ let createdNewUsers = [];
165
+
166
+ // validate user data
167
+ for (const newUserData of users) {
168
+ if (!newUserData.role || newUserData.role !== 'service_admin') {
169
+ const { errors } = validateUser(newUserData);
170
+
171
+ if (errors?.length) {
172
+ validationErrors.push(errors);
173
+ continue;
174
+ }
175
+ } else {
176
+ const { errors } = validateServiceAdminUser(newUserData);
177
+
178
+ if (errors?.length) {
179
+ validationErrors.push(errors);
180
+ continue;
181
+ }
182
+ }
183
+
184
+ // ensure emails are unique
185
+ const existingUser = await user.getByEmail({email: newUserData.email});
186
+
187
+ if (existingUser) {
188
+ validationErrors.push(`User with email ${newUserData.email} already exists`);
189
+ }
190
+ }
191
+
192
+ if (validationErrors.length) {
193
+ throw new error.badRequest(`Invalid user data: ${validationErrors}`);
194
+ }
195
+
196
+ // add users
197
+ for (const newUserData of users) {
198
+ const newUser = await user.create(newUserData);
199
+ createdNewUsers.push(newUser);
200
+ }
201
+
202
+ return createdNewUsers;
203
+ });
204
+
205
+ /**
206
+ * Update user
207
+ *
208
+ * @param {Object} params
209
+ * @param {string|number} params.id - User ID
210
+ * @param {Object[]} params.newUserData - New user data
211
+ * @returns {Promise<Array>} Array of new users
212
+ */
213
+ commands.register('userService.updateUser', async ({id, newUserData}) => {
214
+
215
+ const validationErrors = [];
216
+
217
+ // check request includes user id
218
+ if (!id) {
219
+ throw new error.badRequest('User ID required');
220
+ }
221
+
222
+ // check user exists
223
+ const userObj = await user.getById({id: id});
224
+
225
+ if (!userObj) {
226
+ throw new error.notFound('User not found');
227
+ }
228
+
229
+ // remove id from new user data
230
+ delete newUserData.id;
231
+
232
+ // validate user data
233
+ if (newUserData.role == 'service_admin') {
234
+ const { errors } = validateServiceAdminUserUpdate(newUserData);
235
+
236
+ if (errors?.length) {
237
+ validationErrors.push(errors);
238
+ }
239
+ } else {
240
+ const { errors } = validateUserUpdate(newUserData);
241
+
242
+ if (errors?.length) {
243
+ validationErrors.push(errors);
244
+ }
245
+ }
246
+
247
+ // ensure emails are unique if being updated
248
+ if (newUserData.email && newUserData.email !== userObj.email) {
249
+ const existingUser = await user.getByEmail({email: newUserData.email});
250
+ logger.info('Existing user with this email:' + existingUser);
251
+
252
+ if (existingUser) {
253
+ validationErrors.push(`User with email ${newUserData.email} already exists`);
254
+ }
255
+ }
256
+
257
+ if (validationErrors.length) {
258
+ throw new error.badRequest(`Invalid user data: ${validationErrors}`);
259
+ }
260
+
261
+ // update
262
+ return await user.update({
263
+ id: id,
264
+ username: newUserData.username ?? userObj.username ?? '',
265
+ email: newUserData.email ?? userObj.email ?? '',
266
+ role: newUserData.role ?? userObj.role ?? config.defaultUserRole
267
+ });
268
+ });
269
+
270
+ /**
271
+ * Delete user
272
+ *
273
+ * @param {Object} params
274
+ * @param {string|number} params.id - User ID
275
+ * @returns {Promise<Boolean>} Success
276
+ */
277
+ commands.register('userService.deleteUser', async ({id}) => {
278
+
279
+ const userObj = await user.getById({id: id});
280
+
281
+ if (!userObj) {
282
+ throw new error.notFound('User not found');
283
+ }
284
+
285
+ return await user.delete({id: id});
286
+ });
@@ -0,0 +1,73 @@
1
+
2
+ /**
3
+ * Gnar Engine Service Config
4
+ */
5
+ export const config = {
6
+ // service name
7
+ serviceName: 'userService',
8
+
9
+ // microservice | modular-monolith
10
+ architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
11
+
12
+ // web server
13
+ http: {
14
+ allowedOrigins: [],
15
+ allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
16
+ allowedHeaders: ['Content-Type', 'Authorization'],
17
+ rateLimiting: {
18
+ max: 5,
19
+ timeWindow: '1 minute',
20
+ }
21
+ },
22
+
23
+ // database
24
+ db: {
25
+ // type: mongodb | mysql
26
+ type: 'mysql',
27
+
28
+ // MongoDB
29
+ connectionUrl: process.env.USER_MONGO_URL,
30
+ connectionArgs: {},
31
+
32
+ // MySQL
33
+ host: process.env.USER_MYSQL_HOST,
34
+ user: process.env.USER_MYSQL_USER,
35
+ password: process.env.USER_MYSQL_PASSWORD,
36
+ database: process.env.USER_MYSQL_DATABASE,
37
+ connectionLimit: 10,
38
+ queueLimit: 20,
39
+ maxRetries: 5
40
+ },
41
+
42
+ // message broker
43
+ message: {
44
+ url: process.env.RABBITMQ_URL,
45
+ queueName: 'userServiceQueue',
46
+ prefetch: 20
47
+ },
48
+
49
+ webSockets: {
50
+ reconnectInterval: 5000,
51
+ maxInitialConnectionAttempts: 5
52
+ },
53
+
54
+ // service specific config
55
+ userRoles: [
56
+ 'service_admin',
57
+ 'admin',
58
+ 'customer'
59
+ ],
60
+
61
+ publicCanCreateRoles: [
62
+ 'customer'
63
+ ],
64
+
65
+ defaultUserRole: 'customer',
66
+
67
+ authenticationOptions: {
68
+ password_auth_enabled: true,
69
+ api_key_auth_enabled: true
70
+ },
71
+
72
+ hashNameSpace: '8a07b16c-327f-45c5-9484-8d843f57bb4b',
73
+ }