@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.
- package/assets/gnar-engine-logo-white.svg +9 -0
- package/bootstrap/deploy.localdev.yml +51 -0
- package/bootstrap/secrets.localdev.yml +27 -0
- package/bootstrap/services/agent/Dockerfile +23 -0
- package/bootstrap/services/agent/notes.md +28 -0
- package/bootstrap/services/agent/package.json +16 -0
- package/bootstrap/services/agent/src/app.js +52 -0
- package/bootstrap/services/agent/src/commands/agent.handler.js +104 -0
- package/bootstrap/services/agent/src/config.js +52 -0
- package/bootstrap/services/agent/src/controllers/http.controller.js +44 -0
- package/bootstrap/services/agent/src/controllers/message.controller.js +51 -0
- package/bootstrap/services/agent/src/db/migrations/01-init.js +50 -0
- package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +36 -0
- package/bootstrap/services/agent/src/policies/agent.policy.js +13 -0
- package/bootstrap/services/agent/src/schema/Agent.schema.js +17 -0
- package/bootstrap/services/agent/src/services/agent.service.js +259 -0
- package/bootstrap/services/agent/src/services/chatgpt.service.js +46 -0
- package/bootstrap/services/agent/src/services/manifest.service.js +21 -0
- package/bootstrap/services/control/Dockerfile +23 -0
- package/bootstrap/services/control/Dockerfile.prod +37 -0
- package/bootstrap/services/control/README.md +25 -0
- package/bootstrap/services/control/package.json +16 -0
- package/bootstrap/services/control/src/app.js +45 -0
- package/bootstrap/services/control/src/commands/control.handler.js +231 -0
- package/bootstrap/services/control/src/commands/service.handler.js +81 -0
- package/bootstrap/services/control/src/commands/task.handler.js +247 -0
- package/bootstrap/services/control/src/config.js +55 -0
- package/bootstrap/services/control/src/controllers/http.controller.js +228 -0
- package/bootstrap/services/control/src/controllers/message.controller.js +40 -0
- package/bootstrap/services/control/src/db/migrations/01-init.js +50 -0
- package/bootstrap/services/control/src/db/migrations/02-control-service-init.js +60 -0
- package/bootstrap/services/control/src/db/migrations/03-alter-tasks.js +29 -0
- package/bootstrap/services/control/src/policies/task.policy.js +53 -0
- package/bootstrap/services/control/src/schema/control.schema.js +42 -0
- package/bootstrap/services/control/src/services/registry.service.js +83 -0
- package/bootstrap/services/control/src/services/reset.service.js +28 -0
- package/bootstrap/services/control/src/services/task.service.js +153 -0
- package/bootstrap/services/control/src/tests/control.test.js +50 -0
- package/bootstrap/services/notification/Dockerfile +23 -0
- package/bootstrap/services/notification/Dockerfile.prod +37 -0
- package/bootstrap/services/notification/README.md +3 -0
- package/bootstrap/services/notification/package.json +34 -0
- package/bootstrap/services/notification/src/app.js +51 -0
- package/bootstrap/services/notification/src/commands/command-bus.js +20 -0
- package/bootstrap/services/notification/src/commands/handlers/control.handler.js +18 -0
- package/bootstrap/services/notification/src/commands/handlers/notification.handler.js +157 -0
- package/bootstrap/services/notification/src/config.js +15 -0
- package/bootstrap/services/notification/src/controllers/message.controller.js +82 -0
- package/bootstrap/services/notification/src/services/logger.service.js +16 -0
- package/bootstrap/services/notification/src/services/ses.service.js +23 -0
- package/bootstrap/services/notification/src/templates/admin-order-recieved.hbs +136 -0
- package/bootstrap/services/notification/src/templates/admin-subscription-failed.hbs +87 -0
- package/bootstrap/services/notification/src/templates/customer-order-recieved.hbs +132 -0
- package/bootstrap/services/notification/src/templates/customer-subscription-failed.hbs +77 -0
- package/bootstrap/services/notification/src/tests/notification.test.js +0 -0
- package/bootstrap/services/portal/Dockerfile +23 -0
- package/bootstrap/services/portal/Dockerfile.remote +40 -0
- package/bootstrap/services/portal/README.md +22 -0
- package/bootstrap/services/portal/nginx.conf +12 -0
- package/bootstrap/services/portal/package.json +59 -0
- package/bootstrap/services/portal/public/favicon.ico +0 -0
- package/bootstrap/services/portal/public/gnar-white.png +0 -0
- package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
- package/bootstrap/services/portal/public/index.html +43 -0
- package/bootstrap/services/portal/public/logo192.png +0 -0
- package/bootstrap/services/portal/public/logo512.png +0 -0
- package/bootstrap/services/portal/public/manifest.json +25 -0
- package/bootstrap/services/portal/public/robots.txt +3 -0
- package/bootstrap/services/portal/src/App.js +56 -0
- package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +1 -0
- package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +1 -0
- package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +1 -0
- package/bootstrap/services/portal/src/assets/activity.svg +3 -0
- package/bootstrap/services/portal/src/assets/arrow.svg +3 -0
- package/bootstrap/services/portal/src/assets/bin-white.svg +3 -0
- package/bootstrap/services/portal/src/assets/bin.svg +3 -0
- package/bootstrap/services/portal/src/assets/check.svg +3 -0
- package/bootstrap/services/portal/src/assets/chevron.svg +3 -0
- package/bootstrap/services/portal/src/assets/contact.svg +3 -0
- package/bootstrap/services/portal/src/assets/dots-vertical.svg +5 -0
- package/bootstrap/services/portal/src/assets/eye-off.svg +3 -0
- package/bootstrap/services/portal/src/assets/eye.svg +4 -0
- package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +47 -0
- package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +47 -0
- package/bootstrap/services/portal/src/assets/gnar_engine.svg +3 -0
- package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
- package/bootstrap/services/portal/src/assets/home.svg +3 -0
- package/bootstrap/services/portal/src/assets/link.svg +3 -0
- package/bootstrap/services/portal/src/assets/lock.svg +3 -0
- package/bootstrap/services/portal/src/assets/package.svg +4 -0
- package/bootstrap/services/portal/src/assets/raffle.svg +3 -0
- package/bootstrap/services/portal/src/assets/settings.svg +4 -0
- package/bootstrap/services/portal/src/assets/shopping-bag.svg +3 -0
- package/bootstrap/services/portal/src/assets/user-black.svg +3 -0
- package/bootstrap/services/portal/src/assets/user.svg +3 -0
- package/bootstrap/services/portal/src/assets/users.svg +3 -0
- package/bootstrap/services/portal/src/assets/wallet.svg +3 -0
- package/bootstrap/services/portal/src/css/style.css +1007 -0
- package/bootstrap/services/portal/src/data/data.js +70 -0
- package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +32 -0
- package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +160 -0
- package/bootstrap/services/portal/src/features/crud/crudEdit.less +230 -0
- package/bootstrap/services/portal/src/features/crud/crudList.less +134 -0
- package/bootstrap/services/portal/src/features/crud/crudPage.less +31 -0
- package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +108 -0
- package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +243 -0
- package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +109 -0
- package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +315 -0
- package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +104 -0
- package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +388 -0
- package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +104 -0
- package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +208 -0
- package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +110 -0
- package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +261 -0
- package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +107 -0
- package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +402 -0
- package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +30 -0
- package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +113 -0
- package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +56 -0
- package/bootstrap/services/portal/src/features/loginForm/loginForm.less +56 -0
- package/bootstrap/services/portal/src/features/notes/Notes.jsx +18 -0
- package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +96 -0
- package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +74 -0
- package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +102 -0
- package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +24 -0
- package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +99 -0
- package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +46 -0
- package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +64 -0
- package/bootstrap/services/portal/src/features/sidebar/sidebar.less +49 -0
- package/bootstrap/services/portal/src/features/skus/Skus.jsx +109 -0
- package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +44 -0
- package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +32 -0
- package/bootstrap/services/portal/src/features/user/User.jsx +54 -0
- package/bootstrap/services/portal/src/features/user/user.less +57 -0
- package/bootstrap/services/portal/src/includes/utilities.js +259 -0
- package/bootstrap/services/portal/src/index.js +14 -0
- package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +50 -0
- package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +17 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +48 -0
- package/bootstrap/services/portal/src/layouts/loginLayout.less +33 -0
- package/bootstrap/services/portal/src/layouts/portalLayout.less +67 -0
- package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +199 -0
- package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +17 -0
- package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +10 -0
- package/bootstrap/services/portal/src/pages/login/Login.jsx +15 -0
- package/bootstrap/services/portal/src/pages/login/login.less +10 -0
- package/bootstrap/services/portal/src/pages/orders/Orders.jsx +199 -0
- package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +15 -0
- package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +15 -0
- package/bootstrap/services/portal/src/pages/payments/Payments.jsx +10 -0
- package/bootstrap/services/portal/src/pages/portal/Portal.jsx +43 -0
- package/bootstrap/services/portal/src/pages/products/Products.jsx +212 -0
- package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +124 -0
- package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +186 -0
- package/bootstrap/services/portal/src/pages/reports/Reports.jsx +10 -0
- package/bootstrap/services/portal/src/pages/settings/Settings.jsx +10 -0
- package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +199 -0
- package/bootstrap/services/portal/src/pages/users/Users.jsx +193 -0
- package/bootstrap/services/portal/src/pages/users/users.less +25 -0
- package/bootstrap/services/portal/src/slices/authSlice.js +71 -0
- package/bootstrap/services/portal/src/store/configureStore.js +12 -0
- package/bootstrap/services/portal/src/styles/global.less +159 -0
- package/bootstrap/services/portal/src/styles/inputs.less +157 -0
- package/bootstrap/services/portal/src/styles/main.less +26 -0
- package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +97 -0
- package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +23 -0
- package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +17 -0
- package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +42 -0
- package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +63 -0
- package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +63 -0
- package/bootstrap/services/portal/src/ui/customSelect/CustomSelect.jsx +63 -0
- package/bootstrap/services/portal/src/ui/customSelect/customSelect.less +92 -0
- package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +19 -0
- package/bootstrap/services/portal/src/ui/loader/Loader.jsx +12 -0
- package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +23 -0
- package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +29 -0
- package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +69 -0
- package/bootstrap/services/portal/src/ui/saveButton/saveButton.less +0 -0
- package/bootstrap/services/rabbit-mq/Dockerfile.prod +9 -0
- package/bootstrap/services/user/Dockerfile +23 -0
- package/bootstrap/services/user/Dockerfile.prod +37 -0
- package/bootstrap/services/user/README.md +8 -0
- package/bootstrap/services/user/package.json +16 -0
- package/bootstrap/services/user/src/app.js +45 -0
- package/bootstrap/services/user/src/commands/session.handler.js +23 -0
- package/bootstrap/services/user/src/commands/user.handler.js +286 -0
- package/bootstrap/services/user/src/config.js +73 -0
- package/bootstrap/services/user/src/controllers/http.controller.js +156 -0
- package/bootstrap/services/user/src/controllers/message.controller.js +51 -0
- package/bootstrap/services/user/src/db/migrations/01-init.js +50 -0
- package/bootstrap/services/user/src/db/migrations/02-user-service-init.js +63 -0
- package/bootstrap/services/user/src/db/migrations/03-unauth-sessions.js +43 -0
- package/bootstrap/services/user/src/db/seeders/development/01-root-user.js +29 -0
- package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +27 -0
- package/bootstrap/services/user/src/db/seeders/production/01-root-user.js +29 -0
- package/bootstrap/services/user/src/policies/user.policy.js +81 -0
- package/bootstrap/services/user/src/schema/user.schema.js +69 -0
- package/bootstrap/services/user/src/services/authentication.service.js +127 -0
- package/bootstrap/services/user/src/services/session.service.js +58 -0
- package/bootstrap/services/user/src/services/user.service.js +130 -0
- package/bootstrap/services/user/src/tests/user.test.js +126 -0
- package/package.json +3 -7
- package/src/agent/agent.client.js +28 -0
- package/src/agent/commands.js +48 -0
- package/src/cli.js +30 -0
- package/src/config.js +8 -0
- package/src/control/commands.js +156 -0
- package/src/control/control.client.js +127 -0
- package/src/dev/commands.js +71 -0
- package/src/dev/dev.service.js +320 -0
- package/src/engine/infra.js +142 -0
- package/src/helpers/helpers.js +63 -0
- package/src/profiles/command.js +170 -0
- package/src/profiles/profiles.client.js +101 -0
- package/src/scaffolder/commands.js +123 -0
- package/src/scaffolder/scaffolder.handler.js +252 -0
- package/src/services/client.js +174 -0
- package/templates/service/Dockerfile.hbs +20 -0
- package/templates/service/app.js.hbs +38 -0
- package/templates/service/commands/{{serviceName}}.handler.js.hbs +97 -0
- package/templates/service/config.js.hbs +48 -0
- package/templates/service/controllers/http.controller.js.hbs +87 -0
- package/templates/service/controllers/message.controller.js.hbs +51 -0
- package/templates/service/db/migrations/01-init.js.hbs +50 -0
- package/templates/service/db/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +23 -0
- package/templates/service/package.json.hbs +18 -0
- package/templates/service/policies/{{serviceName}}.policy.js.hbs +49 -0
- package/templates/service/schema/{{serviceName}}.schema.js.hbs +14 -0
- package/templates/service/services/{{serviceName}}.service.js.hbs +32 -0
- 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;
|
|
File without changes
|
|
@@ -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,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
|
+
}
|