@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,89 @@
1
+ import { commands } from '@gnar-engine/core';
2
+ import { authorise } from '../policies/page.policy.js';
3
+
4
+ /**
5
+ * HTTP controller
6
+ */
7
+ export const httpController = {
8
+
9
+ /**
10
+ * Get single page
11
+ */
12
+ getSingle: {
13
+ method: 'GET',
14
+ url: '/pages/:id',
15
+ preHandler: async (request, reply) => authorise.getSingle(request, reply),
16
+ handler: async (request, reply) => {
17
+ const params = {
18
+ id: request.params.id
19
+ };
20
+ const result = await commands.execute('getSinglePage', params);
21
+ reply.code(200).send({ page: result });
22
+ }
23
+ },
24
+
25
+ /**
26
+ * Get multiple pages
27
+ */
28
+ getMany: {
29
+ method: 'GET',
30
+ url: '/pages/',
31
+ preHandler: async (request, reply) => authorise.getMany(request, reply),
32
+ handler: async (request, reply) => {
33
+ const params = {};
34
+ const results = await commands.execute('getManyPages', params);
35
+ reply.code(200).send({ pages: results });
36
+ }
37
+ },
38
+
39
+ /**
40
+ * Create new page
41
+ */
42
+ create: {
43
+ method: 'POST',
44
+ url: '/pages/',
45
+ preHandler: async (request, reply) => authorise.create(request, reply),
46
+ handler: async (request, reply) => {
47
+ const params = {
48
+ pages: [request.body.page],
49
+ requestUser: request.user
50
+ };
51
+ const results = await commands.execute('createPages', params);
52
+ reply.code(200).send({ pages: results });
53
+ },
54
+ },
55
+
56
+ /**
57
+ * Update page
58
+ */
59
+ update: {
60
+ method: 'POST',
61
+ url: '/pages/:id',
62
+ preHandler: async (request, reply) => authorise.update(request, reply),
63
+ handler: async (request, reply) => {
64
+ const params = {
65
+ id: request.params.id,
66
+ newPageData: request.body.page,
67
+ requestUser: request.user
68
+ };
69
+ const result = await commands.execute('updatePage', params);
70
+ reply.code(200).send({ page: result });
71
+ },
72
+ },
73
+
74
+ /**
75
+ * Delete page
76
+ */
77
+ delete: {
78
+ method: 'DELETE',
79
+ url: '/pages/:id',
80
+ preHandler: async (request, reply) => authorise.delete(request, reply),
81
+ handler: async (request, reply) => {
82
+ const params = {
83
+ id: request.params.id
84
+ };
85
+ await commands.execute('deletePage', params);
86
+ reply.code(200).send({ message: 'Page deleted' });
87
+ },
88
+ },
89
+ }
@@ -0,0 +1,50 @@
1
+ import { config } from '../config.js';
2
+
3
+ export const authorise = {
4
+
5
+ /**
6
+ * Authorise get single block
7
+ */
8
+ getSingle: async (request, reply) => {
9
+ if (!request.user || request.user.role !== 'service_admin') {
10
+ reply.code(403).send({error: 'not authorised'});
11
+ }
12
+ },
13
+
14
+ /**
15
+ * Authorise get many blocks
16
+ */
17
+ getMany: async (request, reply) => {
18
+ if (!request.user || request.user.role !== 'service_admin') {
19
+ reply.code(403).send({error: 'not authorised'});
20
+ }
21
+ },
22
+
23
+ /**
24
+ * Authorise create blocks
25
+ */
26
+ create: async (request, reply) => {
27
+ console.log('request user', request.user);
28
+ if (!request.user || request.user.role !== 'service_admin') {
29
+ reply.code(403).send({error: 'not authorised'});
30
+ }
31
+ },
32
+
33
+ /**
34
+ * Authorise update block
35
+ */
36
+ update: async (request, reply) => {
37
+ if (!request.user || request.user.role !== 'service_admin') {
38
+ reply.code(403).send({error: 'not authorised'});
39
+ }
40
+ },
41
+
42
+ /**
43
+ * Authorise delete block
44
+ */
45
+ delete: async (request, reply) => {
46
+ if (!request.user || request.user.role !== 'service_admin') {
47
+ reply.code(403).send({error: 'not authorised'});
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,49 @@
1
+ import { config } from '../config.js';
2
+
3
+ export const authorise = {
4
+
5
+ /**
6
+ * Authorise get single page
7
+ */
8
+ getSingle: async (request, reply) => {
9
+ if (!request.user || request.user.role !== 'service_admin') {
10
+ reply.code(403).send({error: 'not authorised'});
11
+ }
12
+ },
13
+
14
+ /**
15
+ * Authorise get many pages
16
+ */
17
+ getMany: async (request, reply) => {
18
+ if (!request.user || request.user.role !== 'service_admin') {
19
+ reply.code(403).send({error: 'not authorised'});
20
+ }
21
+ },
22
+
23
+ /**
24
+ * Authorise create pages
25
+ */
26
+ create: async (request, reply) => {
27
+ if (!request.user || request.user.role !== 'service_admin') {
28
+ reply.code(403).send({error: 'not authorised'});
29
+ }
30
+ },
31
+
32
+ /**
33
+ * Authorise update page
34
+ */
35
+ update: async (request, reply) => {
36
+ if (!request.user || request.user.role !== 'service_admin') {
37
+ reply.code(403).send({error: 'not authorised'});
38
+ }
39
+ },
40
+
41
+ /**
42
+ * Authorise delete page
43
+ */
44
+ delete: async (request, reply) => {
45
+ if (!request.user || request.user.role !== 'service_admin') {
46
+ reply.code(403).send({error: 'not authorised'});
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,139 @@
1
+ import { schema } from '@gnar-engine/core';
2
+ import { config } from '../config.js';
3
+
4
+ export const pageSchema = {
5
+ schemaName: 'pageService.pageSchema',
6
+ schema: {
7
+ type: 'object',
8
+ properties: {
9
+ name: { type: 'string' },
10
+ key: { type: 'string' },
11
+ blocks: {
12
+ type: 'array',
13
+ items: { $ref: 'pageService.blockSchema' }
14
+ }
15
+ },
16
+ required: ['name', 'key'],
17
+ additionalProperties: false
18
+ }
19
+ };
20
+
21
+ export const blockSchema = {
22
+ $id: 'pageService.blockSchema',
23
+ schemaName: 'pageService.blockSchema',
24
+ schema: {
25
+ type: 'object',
26
+ properties: {
27
+ name: { type: 'string' },
28
+ key: { type: 'string' },
29
+ blocks: {
30
+ type: 'array',
31
+ items: { $ref: 'pageService.blockSchema' }
32
+ },
33
+ fields: {
34
+ type: 'array',
35
+ items: {
36
+ oneOf: [
37
+ { $ref: 'pageService.textInputSchema' },
38
+ { $ref: 'pageService.richTextSchema' },
39
+ { $ref: 'pageService.imageSchema' },
40
+ { $ref: 'pageService.blockSchema' },
41
+ { $ref: 'pageService.repeaterSchema' }
42
+ ]
43
+ }
44
+ },
45
+ type: { type: 'string', enum: ['block'] },
46
+ instanceId: { type: 'string' }
47
+ },
48
+ required: ['name', 'key'],
49
+ additionalProperties: false
50
+ }
51
+ }
52
+
53
+ export const textInputSchema = {
54
+ $id: 'pageService.textInputSchema',
55
+ schemaName: 'pageService.textInputSchema',
56
+ schema: {
57
+ type: 'object',
58
+ properties: {
59
+ key: { type: 'string' },
60
+ name: { type: 'string' },
61
+ type: { type: 'string', enum: ['text'] },
62
+ value: { type: 'string' }
63
+ },
64
+ required: ['key', 'type'],
65
+ additionalProperties: false
66
+ }
67
+ }
68
+
69
+ export const richTextSchema = {
70
+ $id: 'pageService.richTextSchema',
71
+ schemaName: 'pageService.richTextSchema',
72
+ schema: {
73
+ type: 'object',
74
+ properties: {
75
+ key: { type: 'string' },
76
+ name: { type: 'string' },
77
+ type: { type: 'string', enum: ['richtext'] },
78
+ value: { type: 'string' }
79
+ },
80
+ required: ['key', 'type'],
81
+ additionalProperties: false
82
+ }
83
+ }
84
+
85
+ export const imageSchema = {
86
+ $id: 'pageService.imageSchema',
87
+ schemaName: 'pageService.imageSchema',
88
+ schema: {
89
+ type: 'object',
90
+ properties: {
91
+ key: { type: 'string' },
92
+ name: { type: 'string' },
93
+ type: { type: 'string', enum: ['image'] },
94
+ value: {
95
+ type: 'object',
96
+ properties: {
97
+ file: { type: 'string' },
98
+ mimeType: { type: 'string' },
99
+ fileName: { type: 'string' },
100
+ url: { type: 'string' }
101
+ }
102
+ },
103
+ altText: { type: 'string' }
104
+ },
105
+ required: ['key', 'type'],
106
+ additionalProperties: false
107
+ }
108
+ }
109
+
110
+ export const repeaterSchema = {
111
+ $id: 'pageService.repeaterSchema',
112
+ schemaName: 'pageService.repeaterSchema',
113
+ schema: {
114
+ type: 'object',
115
+ properties: {
116
+ key: { type: 'string' },
117
+ name: { type: 'string' },
118
+ type: { type: 'string', enum: ['repeater'] },
119
+ repeaterType: { type: 'string' },
120
+ value: {
121
+ type: 'array',
122
+ items: {
123
+ $ref: 'pageService.blockSchema'
124
+ },
125
+ }
126
+ },
127
+ required: ['key', 'type', 'repeaterType'],
128
+ additionalProperties: false
129
+ }
130
+ }
131
+
132
+ schema.addSchema(blockSchema);
133
+ schema.addSchema(textInputSchema);
134
+ schema.addSchema(richTextSchema);
135
+ schema.addSchema(imageSchema);
136
+ schema.addSchema(repeaterSchema);
137
+
138
+ export const validatePage = schema.compile(pageSchema);
139
+ export const validateBlock = schema.compile(blockSchema);
@@ -0,0 +1,83 @@
1
+ import { db, logger } from '@gnar-engine/core';
2
+ import { ObjectId } from 'mongodb';
3
+
4
+ export const block = {
5
+
6
+ // Get all blocks
7
+ getAll: async () => {
8
+ try {
9
+ const items = await db.collection('blocks').find().toArray();
10
+ return items.map(mappings);
11
+ } catch (error) {
12
+ logger.error("Error fetching blocks:", error);
13
+ throw error;
14
+ }
15
+ },
16
+
17
+ // Create a block
18
+ create: async (data) => {
19
+ try {
20
+ const collection = db.collection('blocks');
21
+ const result = await collection.insertOne(data);
22
+ const insterted = await collection.findOne({ _id: result.insertedId });
23
+ return mappings(insterted);
24
+ } catch (error) {
25
+ logger.error("Error creating block:", error);
26
+ throw error;
27
+ }
28
+ },
29
+
30
+ // Get a block by ID
31
+ getById: async ({ id }) => {
32
+ try {
33
+ const collection = db.collection('blocks');
34
+ const objectId = new ObjectId(id);
35
+ const item = await collection.findOne({ _id: objectId });
36
+ return mappings(item);
37
+ } catch (error) {
38
+ logger.error("Error fetching block:", error);
39
+ throw error;
40
+ }
41
+ },
42
+
43
+ // Update a block
44
+ update: async ({ id, updatedData }) => {
45
+ try {
46
+ const collection = db.collection('blocks');
47
+ const objectId = new ObjectId(id);
48
+ const result = await collection.updateOne(
49
+ { _id: objectId },
50
+ { $set: updatedData }
51
+ );
52
+ return result.modifiedCount > 0;
53
+ } catch (error) {
54
+ logger.error("Error updating block:", error);
55
+ throw error;
56
+ }
57
+ },
58
+
59
+ // Delete a block
60
+ delete: async ({ id }) => {
61
+ try {
62
+ const collection = db.collection('blocks');
63
+ const objectId = new ObjectId(id);
64
+ const result = await collection.deleteOne({ _id: objectId });
65
+ return result.deletedCount > 0;
66
+ } catch (error) {
67
+ logger.error("Error deleting block:", error);
68
+ throw error;
69
+ }
70
+ }
71
+ };
72
+
73
+ const mappings = (item) => {
74
+ if (!item) {
75
+ return item;
76
+ }
77
+
78
+ // _id -> id
79
+ const { _id, ...rest } = item;
80
+ item = { id: _id.toString(), ...rest };
81
+
82
+ return item;
83
+ }
@@ -0,0 +1,83 @@
1
+ import { db, logger } from '@gnar-engine/core';
2
+ import { ObjectId } from 'mongodb';
3
+
4
+ export const page = {
5
+
6
+ // Get all pages
7
+ getAll: async () => {
8
+ try {
9
+ const items = await db.collection('pages').find().toArray();
10
+ return items.map(mappings);
11
+ } catch (error) {
12
+ logger.error("Error fetching pages:", error);
13
+ throw error;
14
+ }
15
+ },
16
+
17
+ // Create a page
18
+ create: async (data) => {
19
+ try {
20
+ const collection = db.collection('pages');
21
+ const result = await collection.insertOne(data);
22
+ const insterted = await collection.findOne({ _id: result.insertedId });
23
+ return mappings(insterted);
24
+ } catch (error) {
25
+ logger.error("Error creating page:", error);
26
+ throw error;
27
+ }
28
+ },
29
+
30
+ // Get a page by ID
31
+ getById: async ({ id }) => {
32
+ try {
33
+ const collection = db.collection('pages');
34
+ const objectId = new ObjectId(id);
35
+ const item = await collection.findOne({ _id: objectId });
36
+ return mappings(item);
37
+ } catch (error) {
38
+ logger.error("Error fetching page:", error);
39
+ throw error;
40
+ }
41
+ },
42
+
43
+ // Update a page
44
+ update: async ({ id, updatedData }) => {
45
+ try {
46
+ const collection = db.collection('pages');
47
+ const objectId = new ObjectId(id);
48
+ const result = await collection.updateOne(
49
+ { _id: objectId },
50
+ { $set: updatedData }
51
+ );
52
+ return result.modifiedCount > 0;
53
+ } catch (error) {
54
+ logger.error("Error updating page:", error);
55
+ throw error;
56
+ }
57
+ },
58
+
59
+ // Delete a page
60
+ delete: async ({ id }) => {
61
+ try {
62
+ const collection = db.collection('pages');
63
+ const objectId = new ObjectId(id);
64
+ const result = await collection.deleteOne({ _id: objectId });
65
+ return result.deletedCount > 0;
66
+ } catch (error) {
67
+ logger.error("Error deleting page:", error);
68
+ throw error;
69
+ }
70
+ }
71
+ };
72
+
73
+ const mappings = (item) => {
74
+ if (!item) {
75
+ return item;
76
+ }
77
+
78
+ // _id -> id
79
+ const { _id, ...rest } = item;
80
+ item = { id: _id.toString(), ...rest };
81
+
82
+ return item;
83
+ }
@@ -0,0 +1,20 @@
1
+ # Dockerfile for Control 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/portal/ ./
12
+
13
+ # Install app dependencies
14
+ RUN npm install
15
+
16
+ # Expose the port the service will run on
17
+ EXPOSE 5173
18
+
19
+ # Start the application
20
+ CMD ["npm", "run", "start:dev"]
@@ -0,0 +1,73 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x'
51
+ import reactDom from 'eslint-plugin-react-dom'
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ])
73
+ ```
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>portal</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,5 @@
1
+ # portal nginx override
2
+ location /portal {
3
+ rewrite ^/portal$ /portal/ break;
4
+ proxy_pass http://portal-service:5173/portal;
5
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "portal",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host",
8
+ "build": "vite build",
9
+ "build:style": "lessc --strict-imports src/style/main.less src/css/style.css",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@reduxjs/toolkit": "^2.10.1",
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0",
16
+ "axios": "^1.13.2",
17
+ "react-redux": "^9.2.0",
18
+ "react-router-dom": "^7.9.6",
19
+ "react-quill-new": "^3.7.0",
20
+ "crypto-js": "^4.2.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^24.10.0",
24
+ "@types/react": "^19.2.2",
25
+ "@types/react-dom": "^19.2.2",
26
+ "@vitejs/plugin-react": "^5.1.0",
27
+ "globals": "^16.5.0",
28
+ "less": "^4.4.2",
29
+ "lessc": "^1.0.2",
30
+ "vite": "^7.2.2",
31
+ "uuid": "^11.1.0"
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1,7 @@
1
+ import type { Config } from "@react-router/dev/config";
2
+
3
+ export default {
4
+ // Config options...
5
+ // Server-side render by default, to enable SPA mode set this to `false`
6
+ ssr: false,
7
+ } satisfies Config;
@@ -0,0 +1,16 @@
1
+ import { useState } from 'react'
2
+ import { Outlet } from "react-router-dom";
3
+ import { Provider } from 'react-redux';
4
+ import store from './store/configureStore';
5
+
6
+ function App() {
7
+ return (
8
+ <>
9
+ <Provider store={store}>
10
+ <Outlet />
11
+ </Provider>
12
+ </>
13
+ )
14
+ }
15
+
16
+ export default App