cyberia 3.0.1 → 3.0.2

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 (49) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +1 -0
  2. package/CHANGELOG.md +56 -1
  3. package/CLI-HELP.md +2 -4
  4. package/README.md +139 -0
  5. package/bin/build.js +5 -0
  6. package/bin/cyberia.js +385 -71
  7. package/bin/deploy.js +18 -26
  8. package/bin/file.js +3 -0
  9. package/bin/index.js +385 -71
  10. package/conf.js +32 -3
  11. package/deployment.yaml +2 -2
  12. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  13. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  14. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  15. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  16. package/manifests/ipfs/configmap.yaml +7 -0
  17. package/package.json +8 -8
  18. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +2 -0
  19. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -0
  20. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +93 -2
  21. package/src/api/file/file.controller.js +3 -13
  22. package/src/api/file/file.ref.json +0 -21
  23. package/src/api/ipfs/ipfs.controller.js +104 -0
  24. package/src/api/ipfs/ipfs.model.js +71 -0
  25. package/src/api/ipfs/ipfs.router.js +31 -0
  26. package/src/api/ipfs/ipfs.service.js +193 -0
  27. package/src/api/object-layer/README.md +139 -0
  28. package/src/api/object-layer/object-layer.controller.js +3 -0
  29. package/src/api/object-layer/object-layer.model.js +15 -1
  30. package/src/api/object-layer/object-layer.router.js +6 -10
  31. package/src/api/object-layer/object-layer.service.js +311 -182
  32. package/src/cli/cluster.js +30 -38
  33. package/src/cli/index.js +0 -1
  34. package/src/cli/run.js +14 -0
  35. package/src/client/components/core/LoadingAnimation.js +2 -3
  36. package/src/client/components/core/Modal.js +1 -1
  37. package/src/client/components/cyberia/ObjectLayerEngineModal.js +4 -5
  38. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +280 -29
  39. package/src/client/services/ipfs/ipfs.service.js +144 -0
  40. package/src/client/services/object-layer/object-layer.management.js +161 -8
  41. package/src/index.js +1 -1
  42. package/src/runtime/express/Express.js +1 -1
  43. package/src/server/auth.js +18 -18
  44. package/src/server/ipfs-client.js +433 -0
  45. package/src/server/object-layer.js +649 -18
  46. package/src/server/semantic-layer-generator.js +1083 -0
  47. package/src/server/shape-generator.js +952 -0
  48. package/test/shape-generator.test.js +457 -0
  49. package/bin/ssl.js +0 -63
@@ -1,15 +1,20 @@
1
1
  import { DefaultManagement } from '../default/default.management.js';
2
2
  import { ObjectLayerService } from './object-layer.service.js';
3
- import { commonUserGuard } from '../../components/core/CommonJs.js';
3
+ import { commonUserGuard, commonModeratorGuard } from '../../components/core/CommonJs.js';
4
4
  import { getProxyPath, setPath, setQueryParams } from '../../components/core/Router.js';
5
5
  import { ObjectLayerEngineModal } from '../../components/cyberia/ObjectLayerEngineModal.js';
6
6
  import { ObjectLayerEngineViewer } from '../../components/cyberia/ObjectLayerEngineViewer.js';
7
7
  import { s } from '../../components/core/VanillaJs.js';
8
8
  import { Modal } from '../../components/core/Modal.js';
9
9
  import { BtnIcon } from '../../components/core/BtnIcon.js';
10
+ import { NotificationManager } from '../../components/core/NotificationManager.js';
11
+ import { AgGrid } from '../../components/core/AgGrid.js';
10
12
 
11
13
  const ObjectLayerManagement = {
12
- RenderTable: async ({ Elements, idModal }) => {
14
+ RenderTable: async ({ Elements, idModal: rawIdModal }) => {
15
+ const idModal = rawIdModal || 'modal-object-layer-engine-management';
16
+ const serviceId = 'object-layer-engine-management';
17
+ const gridId = `${serviceId}-grid-${idModal}`;
13
18
  const user = Elements.Data.user.main.model.user;
14
19
  const { role } = user;
15
20
 
@@ -41,9 +46,9 @@ const ObjectLayerManagement = {
41
46
  // Navigate to viewer route first
42
47
  setPath(`${getProxyPath()}object-layer-engine-viewer`);
43
48
  // Then add query param without replacing history
44
- setQueryParams({ cid: data._id }, { replace: true });
49
+ setQueryParams({ id: data._id }, { replace: true });
45
50
  if (s(`.modal-object-layer-engine-viewer`)) {
46
- await ObjectLayerEngineViewer.Reload({ Elements });
51
+ await ObjectLayerEngineViewer.Reload({ Elements, force: true });
47
52
  }
48
53
  s(`.main-btn-object-layer-engine-viewer`).click();
49
54
  });
@@ -87,7 +92,7 @@ const ObjectLayerManagement = {
87
92
  // Navigate to editor route first
88
93
  setPath(`${getProxyPath()}object-layer-engine`);
89
94
  // Then add query param without replacing history
90
- setQueryParams({ cid: data._id }, { replace: true });
95
+ setQueryParams({ id: data._id }, { replace: true });
91
96
  if (s(`.modal-object-layer-engine`)) await ObjectLayerEngineModal.Reload();
92
97
  else s(`.main-btn-object-layer-engine`).click();
93
98
  });
@@ -148,6 +153,123 @@ const ObjectLayerManagement = {
148
153
  }
149
154
  }
150
155
 
156
+ // Custom renderer for delete button (moderator+ only)
157
+ const canDelete = commonModeratorGuard(role);
158
+
159
+ class DeleteButtonRenderer {
160
+ eGui;
161
+
162
+ async init(params) {
163
+ this.eGui = document.createElement('div');
164
+ const { data } = params;
165
+
166
+ if (!data || !data._id || !canDelete) {
167
+ this.eGui.innerHTML = '';
168
+ return;
169
+ }
170
+
171
+ this.eGui.innerHTML = html` ${await BtnIcon.Render({
172
+ label: html`<div class="abs center">
173
+ <i class="fas fa-trash" style="color: #dc3545;"></i>
174
+ </div> `,
175
+ class: `in fll section-mp management-table-btn-mini btn-delete-object-layer-${data._id}`,
176
+ })}`;
177
+
178
+ setTimeout(() => {
179
+ const btn = this.eGui.querySelector(`.btn-delete-object-layer-${data._id}`);
180
+ if (btn)
181
+ btn.onclick = async () => {
182
+ const itemId = data?.data?.item?.id || data._id;
183
+ const confirmResult = await Modal.RenderConfirm({
184
+ id: `delete-object-layer-${data._id}`,
185
+ html: async () => html`
186
+ <div class="in section-mp" style="text-align: center">
187
+ <p>Are you sure you want to permanently delete object layer <strong>"${itemId}"</strong>?</p>
188
+ <p style="color: #dc3545; font-size: 13px; margin-top: 8px;">
189
+ This will remove all associated data including render frames, atlas sprite sheet, IPFS pins, and
190
+ static asset files.
191
+ </p>
192
+ </div>
193
+ `,
194
+ });
195
+ if (confirmResult.status !== 'confirm') return;
196
+ try {
197
+ const result = await ObjectLayerService.delete({ id: data._id });
198
+ if (result.status === 'success') {
199
+ NotificationManager.Push({
200
+ html: `Object layer "${itemId}" deleted successfully`,
201
+ status: 'success',
202
+ });
203
+ if (AgGrid.grids[gridId]) {
204
+ AgGrid.grids[gridId].applyTransaction({ remove: [data] });
205
+ }
206
+ const token = DefaultManagement.Tokens[idModal];
207
+ if (token) {
208
+ const newTotal = token.total - 1;
209
+ const newTotalPages = Math.ceil(newTotal / token.limit);
210
+ if (token.page > newTotalPages && newTotalPages > 0) {
211
+ token.page = newTotalPages;
212
+ }
213
+ await DefaultManagement.loadTable(idModal, { reload: false });
214
+ }
215
+ } else {
216
+ throw new Error(result.message || 'Failed to delete object layer');
217
+ }
218
+ } catch (error) {
219
+ NotificationManager.Push({
220
+ html: `Failed to delete: ${error.message}`,
221
+ status: 'error',
222
+ });
223
+ }
224
+ };
225
+ });
226
+ }
227
+
228
+ getGui() {
229
+ return this.eGui;
230
+ }
231
+
232
+ refresh(params) {
233
+ return true;
234
+ }
235
+ }
236
+
237
+ const createCidRenderer = (cidAccessor) => {
238
+ return class {
239
+ eGui;
240
+
241
+ async init(params) {
242
+ this.eGui = document.createElement('div');
243
+ const { data } = params;
244
+ const cid = cidAccessor(data) || '';
245
+
246
+ if (!cid) {
247
+ this.eGui.innerHTML = html`<span style="color: #666; font-style: italic;">—</span>`;
248
+ return;
249
+ }
250
+
251
+ this.eGui.innerHTML = html`<span
252
+ title="${cid}"
253
+ style="font-family: monospace; font-size: 11px; cursor: default; user-select: all;"
254
+ >${cid}</span
255
+ >`;
256
+ }
257
+
258
+ getGui() {
259
+ return this.eGui;
260
+ }
261
+
262
+ refresh(params) {
263
+ return true;
264
+ }
265
+ };
266
+ };
267
+
268
+ // IPFS CID of object layer data JSON (fast-json-stable-stringify)
269
+ const CidRenderer = createCidRenderer((d) => d?.cid);
270
+ // IPFS CID of the consolidated atlas sprite sheet PNG
271
+ const AtlasCidRenderer = createCidRenderer((d) => d?.data?.atlasSpriteSheetCid || d?.atlasSpriteSheetId?.cid);
272
+
151
273
  let columnDefs = [
152
274
  // {
153
275
  // field: '_id',
@@ -163,6 +285,24 @@ const ObjectLayerManagement = {
163
285
  },
164
286
  { field: 'data.item.type', headerName: 'Item Type', editable: role === 'user' },
165
287
  { field: 'data.item.description', headerName: 'Description', flex: 1, editable: role === 'user' },
288
+ {
289
+ field: 'cid',
290
+ headerName: 'IPFS CID',
291
+ width: 160,
292
+ cellRenderer: CidRenderer,
293
+ editable: false,
294
+ sortable: false,
295
+ filter: false,
296
+ },
297
+ {
298
+ field: 'data.atlasSpriteSheetCid',
299
+ headerName: 'Atlas CID',
300
+ width: 160,
301
+ cellRenderer: AtlasCidRenderer,
302
+ editable: false,
303
+ sortable: false,
304
+ filter: false,
305
+ },
166
306
  {
167
307
  field: 'frame08',
168
308
  headerName: 'Frame 08 Preview',
@@ -190,15 +330,28 @@ const ObjectLayerManagement = {
190
330
  sortable: false,
191
331
  filter: false,
192
332
  },
333
+ ...(canDelete
334
+ ? [
335
+ {
336
+ field: 'delete',
337
+ headerName: '',
338
+ width: 100,
339
+ cellRenderer: DeleteButtonRenderer,
340
+ editable: false,
341
+ sortable: false,
342
+ filter: false,
343
+ },
344
+ ]
345
+ : []),
193
346
  ];
194
347
 
195
348
  return await DefaultManagement.RenderTable({
196
- idModal: idModal ? idModal : 'modal-object-layer-engine-management',
197
- serviceId: 'object-layer-engine-management',
349
+ idModal,
350
+ serviceId,
198
351
  entity: 'object-layer',
199
352
  permissions: {
200
353
  add: commonUserGuard(role),
201
- remove: commonUserGuard(role),
354
+ remove: false,
202
355
  reload: commonUserGuard(role),
203
356
  },
204
357
  customEvent: {
package/src/index.js CHANGED
@@ -42,7 +42,7 @@ class Underpost {
42
42
  * @type {String}
43
43
  * @memberof Underpost
44
44
  */
45
- static version = 'v3.0.1';
45
+ static version = 'v3.0.2';
46
46
 
47
47
  /**
48
48
  * Required Node.js major version
@@ -99,7 +99,7 @@ class ExpressService {
99
99
 
100
100
  if (origins && isDevProxyContext())
101
101
  origins.push(devProxyHostFactory({ host, includeHttp: true, tls: isTlsDevProxy() }));
102
- app.set('trust proxy', true);
102
+ app.set('trust proxy', 1);
103
103
 
104
104
  app.use((req, res, next) => {
105
105
  res.on('finish', () => {
@@ -647,24 +647,24 @@ function applySecurity(app, opts = {}) {
647
647
  }),
648
648
  );
649
649
  logger.info('Cors origin', origin);
650
-
651
- // Rate limiting + slow down
652
- const limiter = rateLimit({
653
- windowMs: rate.windowMs,
654
- max: rate.max,
655
- standardHeaders: true,
656
- legacyHeaders: false,
657
- message: { error: 'Too many requests, please try again later.' },
658
- });
659
- app.use(limiter);
660
-
661
- const speedLimiter = slowDown({
662
- windowMs: slowdown.windowMs,
663
- delayAfter: slowdown.delayAfter,
664
- delayMs: () => slowdown.delayMs,
665
- });
666
- app.use(speedLimiter);
667
-
650
+ if (!process.env.DISABLE_API_RATE_LIMIT) {
651
+ // Rate limiting + slow down
652
+ const limiter = rateLimit({
653
+ windowMs: rate.windowMs,
654
+ max: rate.max,
655
+ standardHeaders: true,
656
+ legacyHeaders: false,
657
+ message: { error: 'Too many requests, please try again later.' },
658
+ });
659
+ app.use(limiter);
660
+
661
+ const speedLimiter = slowDown({
662
+ windowMs: slowdown.windowMs,
663
+ delayAfter: slowdown.delayAfter,
664
+ delayMs: () => slowdown.delayMs,
665
+ });
666
+ app.use(speedLimiter);
667
+ }
668
668
  // Cookie parsing
669
669
  app.use(cookieParser(process.env.JWT_SECRET));
670
670
  }