@gnar-engine/cli 1.0.4 → 1.0.5

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 (119) hide show
  1. package/bootstrap/deploy.localdev.yml +30 -3
  2. package/bootstrap/secrets.localdev.yml +15 -4
  3. package/bootstrap/services/control/src/config.js +4 -0
  4. package/bootstrap/services/page/Dockerfile +23 -0
  5. package/bootstrap/services/page/package.json +16 -0
  6. package/bootstrap/services/page/src/app.js +50 -0
  7. package/bootstrap/services/page/src/commands/block.handler.js +94 -0
  8. package/bootstrap/services/page/src/commands/page.handler.js +167 -0
  9. package/bootstrap/services/page/src/config.js +62 -0
  10. package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
  11. package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
  12. package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
  13. package/bootstrap/services/page/src/policies/block.policy.js +50 -0
  14. package/bootstrap/services/page/src/policies/page.policy.js +49 -0
  15. package/bootstrap/services/page/src/schema/page.schema.js +139 -0
  16. package/bootstrap/services/page/src/services/block.service.js +83 -0
  17. package/bootstrap/services/page/src/services/page.service.js +83 -0
  18. package/bootstrap/services/portal/Dockerfile +20 -0
  19. package/bootstrap/services/portal/README.md +73 -0
  20. package/bootstrap/services/portal/index.html +13 -0
  21. package/bootstrap/services/portal/nginx.conf +5 -0
  22. package/bootstrap/services/portal/package.json +33 -0
  23. package/bootstrap/services/portal/public/vite.svg +1 -0
  24. package/bootstrap/services/portal/react-router.config.js +7 -0
  25. package/bootstrap/services/portal/src/App.jsx +16 -0
  26. package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
  27. package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
  28. package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
  29. package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
  30. package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
  31. package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
  32. package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
  33. package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
  34. package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
  35. package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
  36. package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
  37. package/bootstrap/services/portal/src/assets/react.svg +1 -0
  38. package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
  39. package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
  40. package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.jsx +81 -0
  41. package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.less +0 -0
  42. package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
  43. package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
  44. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
  45. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
  46. package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
  47. package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
  48. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
  49. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
  50. package/bootstrap/services/portal/src/css/style.css +711 -0
  51. package/bootstrap/services/portal/src/data/pages.data.js +10 -0
  52. package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
  53. package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.less +102 -0
  54. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
  55. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
  56. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
  57. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
  58. package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
  59. package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
  60. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
  61. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
  62. package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
  63. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
  64. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
  65. package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
  66. package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
  67. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
  68. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
  69. package/bootstrap/services/portal/src/main.jsx +51 -0
  70. package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
  71. package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
  72. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
  73. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
  74. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
  75. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
  76. package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
  77. package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
  78. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
  79. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
  80. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
  81. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
  82. package/bootstrap/services/portal/src/services/block.js +28 -0
  83. package/bootstrap/services/portal/src/services/client.js +67 -0
  84. package/bootstrap/services/portal/src/services/gravatar.js +14 -0
  85. package/bootstrap/services/portal/src/services/page.js +28 -0
  86. package/bootstrap/services/portal/src/services/storage.js +62 -0
  87. package/bootstrap/services/portal/src/services/user.js +41 -0
  88. package/bootstrap/services/portal/src/slices/authSlice.js +101 -0
  89. package/bootstrap/services/portal/src/store/configureStore.js +10 -0
  90. package/bootstrap/services/portal/src/style/cards.less +57 -0
  91. package/bootstrap/services/portal/src/style/global.less +204 -0
  92. package/bootstrap/services/portal/src/style/icons.less +21 -0
  93. package/bootstrap/services/portal/src/style/inputs.less +52 -0
  94. package/bootstrap/services/portal/src/style/main.less +28 -0
  95. package/bootstrap/services/portal/src/utils/utils.js +9 -0
  96. package/bootstrap/services/portal/vite.config.js +12 -0
  97. package/bootstrap/services/user/src/app.js +6 -1
  98. package/bootstrap/services/user/src/commands/user.handler.js +0 -3
  99. package/bootstrap/services/user/src/config.js +5 -1
  100. package/bootstrap/services/user/src/policies/user.policy.js +3 -1
  101. package/bootstrap/services/user/src/tests/commands/user.test.js +22 -0
  102. package/install-from-clone.sh +30 -0
  103. package/package.json +1 -1
  104. package/src/cli.js +8 -0
  105. package/src/dev/commands.js +10 -2
  106. package/src/dev/dev.service.js +147 -60
  107. package/src/provisioner/Dockerfile +27 -0
  108. package/src/provisioner/package.json +19 -0
  109. package/src/provisioner/src/app.js +56 -0
  110. package/src/provisioner/src/services/mongodb.js +58 -0
  111. package/src/provisioner/src/services/mysql.js +51 -0
  112. package/src/provisioner/src/services/secrets.js +84 -0
  113. package/src/scaffolder/commands.js +1 -1
  114. package/src/scaffolder/scaffolder.handler.js +40 -15
  115. package/templates/service/src/app.js.hbs +12 -1
  116. package/templates/service/src/commands/{{serviceName}}.handler.js.hbs +1 -1
  117. package/templates/service/src/mongodb.config.js.hbs +5 -1
  118. package/templates/service/src/mysql.config.js.hbs +4 -0
  119. package/bootstrap/services/user/src/tests/user.test.js +0 -126
@@ -0,0 +1,101 @@
1
+ import { createSlice, createAsyncThunk, createAction } from '@reduxjs/toolkit';
2
+ import { getAuthToken, getAuthUser, setAuthToken, setAuthUser, removeAuthToken, removeAuthUser } from '../services/storage.js';
3
+ import { user } from '../services/user.js';
4
+
5
+
6
+ export const login = createAsyncThunk('auth/login', async ({ username, password }) => {
7
+ let response;
8
+ try {
9
+ response = await user.authenticate({ username, password });
10
+ } catch (error) {
11
+ response = error.response;
12
+ }
13
+ return response;
14
+ })
15
+
16
+ export const register = createAsyncThunk('auth/register', async (user) => {
17
+ let response;
18
+ try {
19
+ response = await user.createUser(user);
20
+ } catch (error) {
21
+ response = error.response;
22
+ }
23
+ return response;
24
+ })
25
+
26
+ export const logout = createAction('auth/logout');
27
+
28
+ export const authSlice = createSlice({
29
+ name: 'auth',
30
+ initialState: {
31
+ authUser: getAuthUser() ? JSON.parse(getAuthUser()) : null,
32
+ accessToken: getAuthToken() ? getAuthToken() : '',
33
+ authLoading: false,
34
+ authError: ''
35
+ },
36
+ reducers: {
37
+ },
38
+ extraReducers: builder => {
39
+ builder
40
+ .addCase(login.pending, (state, action) => {
41
+ state.authLoading = true;
42
+ state.authError = '';
43
+ })
44
+ .addCase(login.fulfilled, (state, action) => {
45
+ console.log('login.fulfilled', action.payload);
46
+ state.authLoading = false;
47
+ state.authError = action.payload.message ? action.payload.message : '';
48
+
49
+ if (action.payload.token) {
50
+ state.accessToken = action.payload.token;
51
+ state.authUser = action.payload.user;
52
+
53
+ // store in local storage
54
+ setAuthToken(action.payload.token);
55
+ setAuthUser(JSON.stringify(action.payload.user));
56
+
57
+ // redirect to portal
58
+ window.location.href= '/portal/dashboard';
59
+ }
60
+ })
61
+ .addCase(logout, (state, action) => {
62
+ // Clear auth state
63
+ state.authUser = '';
64
+ state.accessToken = '';
65
+
66
+ // Remove from local storage
67
+ removeAuthToken();
68
+ removeAuthUser();
69
+
70
+ // Redirect to login page
71
+ window.location.href = '/portal/login';
72
+ })
73
+
74
+ // Register
75
+ .addCase(register.pending, (state, action) => {
76
+ state.status = 'loading';
77
+ })
78
+ .addCase(register.fulfilled, (state, action) => {
79
+ state.status = 'idle';
80
+
81
+ if (action.payload.users && action.payload.users.length > 0) {
82
+ const user = action.payload.users[0];
83
+
84
+ state.logged_in = true;
85
+ state.user = user;
86
+
87
+ // Save user auth details (if needed)
88
+ setAuthUser(JSON.stringify(user));
89
+
90
+ // Redirect to dashboard page
91
+ window.location.href = '/portal/dashboard';
92
+ } else {
93
+ state.logged_in = false;
94
+ state.user = {};
95
+ state.error = 'Registration failed: Invalid response';
96
+ }
97
+ });
98
+ }
99
+ })
100
+
101
+ export default authSlice;
@@ -0,0 +1,10 @@
1
+ import { configureStore } from '@reduxjs/toolkit';
2
+ import authSlice from '../slices/authSlice';
3
+
4
+ const store = configureStore({
5
+ reducer: {
6
+ auth: authSlice.reducer
7
+ }
8
+ })
9
+
10
+ export default store;
@@ -0,0 +1,57 @@
1
+ .card {
2
+ border: 1px solid #3C3C3C;
3
+ border-radius: 10px;
4
+ margin-bottom: 30px;
5
+ width: 100%;
6
+
7
+ .card-title {
8
+ background: #242424;
9
+ margin: 0px;
10
+ padding: 20px 40px;
11
+ font-size: 16px;
12
+ font-weight: 400;
13
+ border-top-left-radius: 10px;
14
+ border-top-right-radius: 10px;
15
+ overflow: hidden;
16
+ }
17
+
18
+ .card-content {
19
+ padding: 30px 40px;
20
+ }
21
+
22
+ .instruction {
23
+ color: @mid-grey;
24
+ margin-bottom: 30px;
25
+ }
26
+ }
27
+
28
+ .card-columns {
29
+ display: flex;
30
+ flex-wrap: no-wrap;
31
+ gap: 50px;
32
+
33
+ & > .col-66 {
34
+ width: 66%;
35
+ }
36
+
37
+ & > .col-33 {
38
+ width: 33%;
39
+ }
40
+ p {
41
+ margin-top: 0px;
42
+ }
43
+ }
44
+
45
+ .card {
46
+ input[type="text"],
47
+ input[type="password"],
48
+ input[type="email"],
49
+ textarea {
50
+ margin-top: 8px;
51
+ background: @dark-3;
52
+
53
+ &::placeholder {
54
+ color: @light-grey;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,204 @@
1
+
2
+ /* vars */
3
+ @dark-1: #242424;
4
+ @dark-2: #2C2C2C;
5
+ @dark-3: #242424;
6
+ @dark-grey: #3C3C3C;
7
+ @mid-grey: #868686;
8
+ @light-grey: #D9D9D9;
9
+ @white: #ffffff;
10
+ @green-main: #47DCA6;
11
+ @error-red: #FF4D4F;
12
+
13
+ @content-width: 1550px;
14
+
15
+ /* Fonts */
16
+ // @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
17
+
18
+ html {
19
+ background: @dark-1;
20
+ }
21
+
22
+ body,
23
+ body * {
24
+ font-family: ubuntu, sans-serif !important;
25
+ color: @white;
26
+ font-size: 14px;
27
+ }
28
+
29
+ /* Global styles */
30
+
31
+ h1 {
32
+ margin-top: 0px;
33
+ margin-bottom: 40px;
34
+ font-size: 18px;
35
+ font-weight: 400;
36
+ }
37
+ h2 {
38
+ font-size: 14px;
39
+ font-weight: 500;
40
+ }
41
+ body {
42
+ margin: 0px;
43
+
44
+ &, #root {
45
+ min-height: 100vh;
46
+ }
47
+ }
48
+
49
+ .separator {
50
+ display: inline-block;
51
+ margin-top: 20px;
52
+ margin-bottom: 20px;
53
+ height: 0px;
54
+ border-top: 1px solid @dark-grey;
55
+ width: 100%;
56
+ }
57
+ .content-wrap {
58
+ display: flex;
59
+ justify-content: center;
60
+ }
61
+ .content {
62
+ width: 100%;
63
+ max-width: @content-width;
64
+ margin-left: auto;
65
+ margin-right: auto;
66
+ padding-left: 20px;
67
+ padding-right: 20px;
68
+ }
69
+ .instruction {
70
+ margin-top: 10px;
71
+ margin-bottom: 10px;
72
+ color: @light-grey;
73
+ }
74
+ .flex-row {
75
+ display: flex;
76
+ flex-direction: row;
77
+ justify-content: space-between;
78
+ gap: 20px;
79
+ }
80
+ .flex-row-end{
81
+ align-items: flex-end;
82
+ }
83
+ .form-cont {
84
+ width: 100%;
85
+ margin-bottom: 30px;
86
+ }
87
+
88
+ .full-width-select-cont,
89
+ .full-width-select-cont .custom-select {
90
+ min-width: 100%;
91
+ }
92
+ .flex-row-select-cont {
93
+ min-width: 100%;
94
+ display: flex;
95
+ flex-direction: row;
96
+ justify-content: space-between;
97
+ gap: 20px;
98
+
99
+ .custom-select {
100
+ width: 100%;
101
+ max-width: unset;
102
+ }
103
+ }
104
+
105
+ .flex-row-checkbox-cont {
106
+ min-width: 100%;
107
+ display: flex;
108
+ flex-direction: row;
109
+ justify-content: space-between;
110
+ gap: 20px;
111
+
112
+ label {
113
+ display: inline-block;
114
+ }
115
+ }
116
+
117
+ .flex-row-buttons-cont {
118
+ display: flex;
119
+ flex-direction: row;
120
+ justify-content: flex-end;
121
+ gap: 20px;
122
+ }
123
+
124
+ .button {
125
+ &.button-loading {
126
+ background: @dark-grey !important;
127
+ cursor: not-allowed !important;
128
+
129
+ &:disabled {
130
+ cursor: not-allowed;
131
+ opacity: 0.5;
132
+ }
133
+ }
134
+ }
135
+
136
+ .text-link {
137
+ color: @light-grey;
138
+ cursor: pointer;
139
+ display: inline-block;
140
+ text-decoration: none;
141
+ font-size: 14px;
142
+
143
+ &:hover {
144
+ opacity: 0.8;
145
+ }
146
+ }
147
+
148
+ .input {
149
+ font-size: 12px;
150
+ color: @dark-2;
151
+
152
+ input {
153
+ background: @dark-1 !important;
154
+ color: white;
155
+ padding: 10px;
156
+ padding-left: 48px;
157
+ border-radius: 20px;
158
+ font-size: 14px;
159
+ margin-top: 5px;
160
+ margin-bottom: 5px !important;
161
+ }
162
+
163
+ &.icon {
164
+ background-size: 15px;
165
+ background-repeat: no-repeat;
166
+ background-position: 15px 15px;
167
+
168
+ input {
169
+ padding-left: 50px;
170
+ }
171
+ }
172
+ }
173
+
174
+ .single-crud-page {
175
+ .top-bar {
176
+ margin-bottom: 20px;
177
+ }
178
+ .bottom-bar {
179
+ margin-top: 20px;
180
+ margin-bottom: 20px;
181
+ justify-content: flex-end;
182
+ }
183
+ }
184
+
185
+ .error-messages {
186
+ display: flex;
187
+ flex-direction: row;
188
+ justify-content: flex-end;
189
+
190
+ ul {
191
+ list-style: none;
192
+ margin: 0px;
193
+ }
194
+
195
+ li, a, span, p {
196
+ color: @error-red;
197
+ }
198
+ }
199
+
200
+ .button-group {
201
+ button {
202
+ margin-left: 15px;
203
+ }
204
+ }
@@ -0,0 +1,21 @@
1
+ .icon-dashboard {
2
+ background: url('../assets/icon-home.svg');
3
+ }
4
+ .icon-users {
5
+ background: url('../assets/icon-users.svg');
6
+ }
7
+ .icon-settings {
8
+ background: url('../assets/icon-settings.svg');
9
+ }
10
+ .icon-reports {
11
+ background: url('../assets/icon-reports.svg');
12
+ }
13
+ .icon-agent {
14
+ background: url('../assets/icon-agent.svg');
15
+ }
16
+ .icon-page {
17
+ background: url('../assets/icon-page.svg');
18
+ }
19
+ .icon-delete {
20
+ background: url('../assets/icon-delete.svg');
21
+ }
@@ -0,0 +1,52 @@
1
+ input[type="text"],
2
+ input[type="password"],
3
+ input[type="email"],
4
+ textarea,
5
+ .custom-select .custom-select-input {
6
+ border: 1px solid @dark-grey;
7
+ border-radius: 17px;
8
+ padding: 8px 10px;
9
+ font-size: 14px;
10
+ width: 100%;
11
+ box-sizing: border-box;
12
+ background: none;
13
+ margin-bottom: 25px;
14
+ transition: border-color 0.3s;
15
+
16
+ &::placeholder {
17
+ color: @white;
18
+ }
19
+
20
+ &:focus {
21
+ outline: none;
22
+ border-color: @green-main;
23
+ }
24
+ }
25
+
26
+ button {
27
+ background: @green-main;
28
+ border: 1px solid @green-main;
29
+ border-radius: 17px;
30
+ color: @dark-1;
31
+ padding: 9px 19px;
32
+ min-width: 143px;
33
+ font-weight: 700;
34
+ transition: opacity 0.3s;
35
+
36
+ &[type="submit"] {
37
+ margin-top: 25px;
38
+ margin-bottom: 25px;
39
+ }
40
+
41
+ &:hover {
42
+ opacity: 0.8;
43
+ cursor: pointer;
44
+ }
45
+
46
+ &.secondary-btn {
47
+ background: @dark-2;
48
+ color: @white;
49
+ border: 1px solid @dark-grey;
50
+ }
51
+ }
52
+
@@ -0,0 +1,28 @@
1
+ // GLOBAL
2
+ @import "global.less";
3
+ @import "inputs.less";
4
+ @import "icons.less";
5
+ @import "cards.less";
6
+
7
+ // LAYOUTS
8
+ @import "../layouts/PortalLayout/PortalLayout.less";
9
+
10
+ // COMPONENTS
11
+ @import "../components/Sidebar/Sidebar.less";
12
+ @import "../components/Topbar/Topbar.less";
13
+ @import "../components/UserInfo/UserInfo.less";
14
+ @import "../components/CrudList/CrudList.less";
15
+
16
+ // ELEMENTS
17
+ @import "../elements/Repeater/Repeater.less";
18
+ @import "../elements/CustomSelect/CustomSelect.less";
19
+ @import "../elements/RichTextInput/RichTextInput.less";
20
+ @import "../elements/ImageInput/ImageInput.less";
21
+ @import "../elements/ImageMultiInput/ImageMultiInput.less";
22
+
23
+ // PAGES
24
+ @import "../pages/LoginPage/LoginPage.less";
25
+
26
+ #MagiCSS-bookmarklet span {
27
+ color: black !important;
28
+ }
@@ -0,0 +1,9 @@
1
+
2
+ export const fileToBase64 = (file) => {
3
+ return new Promise((resolve, reject) => {
4
+ const reader = new FileReader();
5
+ reader.onload = () => resolve(reader.result); // returns "data:image/png;base64,..."
6
+ reader.onerror = reject;
7
+ reader.readAsDataURL(file);
8
+ });
9
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ server: {
8
+ host: true, // binds to 0.0.0.0
9
+ allowedHosts: ['.localhost', 'portal-service'], // allow extra hosts
10
+ },
11
+ base: '/portal/',
12
+ })
@@ -1,4 +1,4 @@
1
- import { message, http, logger, db, registerService, webSockets } from '@gnar-engine/core';
1
+ import { message, http, logger, db, registerService, webSockets, test } from '@gnar-engine/core';
2
2
  import { config } from './config.js';
3
3
  import { messageHandlers } from './controllers/message.controller.js';
4
4
  import { httpController as userPlatformHttpController } from './controllers/http.controller.js';
@@ -40,6 +40,11 @@ export const initService = async () => {
40
40
  await registerService();
41
41
 
42
42
  logger.info('G n a r E n g i n e | User Service initialised successfully.');
43
+
44
+ // Tests
45
+ if (config.environment === 'test' && config.runTests) {
46
+ test.runCommandTests({config});
47
+ }
43
48
  }
44
49
 
45
50
  initService();
@@ -76,7 +76,6 @@ commands.register('userService.getAuthenticatedUser', async ({token}) => {
76
76
  const user_id = await auth.getAuthenticatedUser(token);
77
77
 
78
78
  if (user_id) {
79
- logger.info('Auth user: ' + user_id);
80
79
  const userObj = await user.getById({id: user_id});
81
80
 
82
81
  if (userObj) {
@@ -86,8 +85,6 @@ commands.register('userService.getAuthenticatedUser', async ({token}) => {
86
85
  return userObj;
87
86
  }
88
87
  }
89
-
90
- return;
91
88
  });
92
89
 
93
90
 
@@ -6,12 +6,16 @@ export const config = {
6
6
  // service name
7
7
  serviceName: 'userService',
8
8
 
9
+ // environment
10
+ environment: process.env.USER_NODE_ENV || 'dev',
11
+ runTests: process.env.USER_RUN_TESTS || false,
12
+
9
13
  // microservice | modular-monolith
10
14
  architecture: process.env.GLOBAL_ARCHITECTURE || 'microservice',
11
15
 
12
16
  // web server
13
17
  http: {
14
- allowedOrigins: [],
18
+ allowedOrigins: ['localhost', 'localhost:4003'],
15
19
  allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
16
20
  allowedHeaders: ['Content-Type', 'Authorization'],
17
21
  rateLimiting: {
@@ -1,4 +1,5 @@
1
1
  import { config } from '../config.js';
2
+ import { logger } from '@gnar-engine/core';
2
3
 
3
4
 
4
5
  export const authorise = {
@@ -23,6 +24,7 @@ export const authorise = {
23
24
  * Authorise get many users
24
25
  */
25
26
  getMany: async (request, reply) => {
27
+ logger.info('user -' + JSON.stringify(request.user));
26
28
  if (!request.user || request.user.role !== 'service_admin') {
27
29
  reply.code(403).send({error: 'not authorised'});
28
30
  }
@@ -78,4 +80,4 @@ export const authorise = {
78
80
  reply.code(403).send({error: 'not authorised'});
79
81
  }
80
82
  }
81
- }
83
+ }
@@ -0,0 +1,22 @@
1
+ import { commands, logger, test } from '@gnar-engine/core';
2
+
3
+ // Preparation before tests run
4
+ test.prep(async () => {
5
+ if (process.env.NODE_ENV === 'production') {
6
+ throw new Error('Do not run tests in production mode!');
7
+ }
8
+ })
9
+
10
+ // Test create user command
11
+ test.run('Create User Command', async () => {
12
+ const users = await commands.execute('createUsers', [
13
+ {
14
+ email: 'test@gnar.co.uk'
15
+ }
16
+ ]);
17
+
18
+ test.assert(users.length === 1, 'User was not created successfully');
19
+ test.assert(users[0].email === 'test@gnar.co.uk', 'User email does not match');
20
+ });
21
+
22
+
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ TARGET_DIR="$HOME/.gnarengine"
5
+ CLI_DIR="$(pwd)"
6
+
7
+ echo "Installing Gnar Engine CLI from local source for user $USER..."
8
+
9
+ mkdir -p "$TARGET_DIR"
10
+
11
+ # Install dependencies
12
+ cd "$CLI_DIR"
13
+ npm install
14
+
15
+ # Link CLI to custom global folder
16
+ npm link --prefix "$TARGET_DIR"
17
+
18
+ # Bin path for npm link
19
+ BIN_PATH="$TARGET_DIR/bin"
20
+
21
+ # Add to shell PATH if not already present
22
+ for SHELLRC in "$HOME/.bashrc" "$HOME/.zshrc"; do
23
+ [ -f "$SHELLRC" ] || continue
24
+ if ! grep -q "$BIN_PATH" "$SHELLRC"; then
25
+ echo "export PATH=\"$BIN_PATH:\$PATH\"" >>"$SHELLRC"
26
+ echo "Added $BIN_PATH to PATH in $SHELLRC"
27
+ fi
28
+ done
29
+
30
+ echo "Gnar Engine CLI installed! Restart your terminal and run 'gnar --help'"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnar-engine/cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Gnar Engine Development Framework CLI: Project bootstrap, scaffolder & control plane.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -6,6 +6,7 @@ import { registerDevCommands } from './dev/commands.js';
6
6
  import { registerControlCommands } from './control/commands.js';
7
7
  import { registerScaffolderCommands } from './scaffolder/commands.js';
8
8
  import { registerAgentCommands } from './agent/commands.js';
9
+ import path from 'path';
9
10
 
10
11
  // Create a new program
11
12
  const program = new Command();
@@ -28,3 +29,10 @@ G n a r E n g i n e - A powerful, AI ready microservice framework for modern a
28
29
 
29
30
  // Parse CLI input
30
31
  program.parse(process.argv);
32
+
33
+ // Consts
34
+ export const directories = {
35
+ scaffolderTemplates: path.join(import.meta.dirname, '../templates/service'),
36
+ bootstrap: path.join(import.meta.dirname, '../bootstrap'),
37
+ provisioner: path.join(import.meta.dirname, './provisioner')
38
+ }
@@ -12,6 +12,8 @@ export function registerDevCommands(program) {
12
12
  .description('🛠️ Up Development Containers')
13
13
  .option('-b, --build', 'Ruild without cache')
14
14
  .option('-d, --detach', 'Run containers in background')
15
+ .option('-t --test', 'Run the tests with ephemeral databases')
16
+ .option('--test-service <service>', 'Run the tests for the specified service with ephemeral databases')
15
17
  .addOption(new Option('--core-dev').hideHelp())
16
18
  .action(async (options) => {
17
19
  let response = {};
@@ -27,13 +29,19 @@ export function registerDevCommands(program) {
27
29
  // Change to the active profile directory
28
30
  const projectDir = activeProfile.PROJECT_DIR;
29
31
 
32
+ if (options.testService) {
33
+ options.test = true;
34
+ }
35
+
30
36
  try {
31
37
  up({
32
38
  projectDir: projectDir,
33
39
  build: options.build || false,
34
40
  detach: options.detach || false,
35
- coreDev: options.coreDev || false
36
- });
41
+ coreDev: options.coreDev || false,
42
+ test: options.test || false,
43
+ testService: options.testService || ''
44
+ });
37
45
  } catch (err) {
38
46
  console.error("❌ Error running containers:", err.message);
39
47
  process.exit(1);