@raclettejs/core 0.1.34 → 0.1.36-canary.0

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 (107) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/dist/cli.js +86 -82
  3. package/dist/cli.js.map +3 -3
  4. package/package.json +5 -5
  5. package/services/.dockerignore +2 -0
  6. package/services/backend/.dockerignore +3 -1
  7. package/services/backend/src/core/payload/payloadTypes.ts +1 -0
  8. package/services/backend/src/core/pluginSystem/configGenerator/index.ts +12 -11
  9. package/services/backend/src/core/pluginSystem/pluginFastifyInstance.ts +28 -3
  10. package/services/backend/src/core/pluginSystem/pluginLoader.ts +11 -2
  11. package/services/backend/src/core/pluginSystem/pluginTypes.ts +39 -1
  12. package/services/backend/src/core/sockets/createSystemEventHandlers.ts +4 -4
  13. package/services/backend/src/core/sockets/socketManager.ts +80 -32
  14. package/services/backend/src/core/sockets/socketTypes.ts +6 -0
  15. package/services/backend/src/core/validation/schemaGenerator.ts +10 -3
  16. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.service.ts +1 -1
  17. package/services/backend/src/corePlugins/raclette__core/backend/index.ts +0 -2
  18. package/services/backend/src/modules/authentication/index.ts +15 -23
  19. package/services/backend/src/shared/helper/adminTag.ts +34 -0
  20. package/services/backend/src/shared/types/plugins/index.ts +1 -9
  21. package/services/backend/src/utils/request.utils.ts +1 -1
  22. package/services/frontend/.dockerignore +3 -1
  23. package/services/frontend/provision/entrypoint.sh +2 -2
  24. package/services/frontend/src/core/helpers/devMode.ts +1 -3
  25. package/services/frontend/src/core/lib/data/dataApi.ts +27 -16
  26. package/services/frontend/src/core/lib/data/queryApi.ts +17 -8
  27. package/services/frontend/src/core/lib/data/responseTypeHandler.ts +17 -8
  28. package/services/frontend/src/core/lib/data/writeDataHandler.ts +3 -1
  29. package/services/frontend/src/core/lib/dataHelper.ts +51 -12
  30. package/services/frontend/src/core/lib/eventWhitelist.ts +2 -0
  31. package/services/frontend/src/core/lib/httpClient.ts +16 -2
  32. package/services/frontend/src/core/lib/logger.ts +2 -3
  33. package/services/frontend/src/core/lib/userNotifier.ts +20 -0
  34. package/services/frontend/src/core/lib/workSessionUrl.ts +16 -0
  35. package/services/frontend/src/core/main.ts +82 -15
  36. package/services/frontend/src/core/setup/socketBootstrap.ts +83 -0
  37. package/services/frontend/src/core/setup/socketClient.ts +39 -39
  38. package/services/frontend/src/core/setup/socketEvents.ts +152 -158
  39. package/services/frontend/src/core/setup/socketRegistry.ts +19 -0
  40. package/services/frontend/src/core/store/index.ts +2 -4
  41. package/services/frontend/src/core/types/DataApi.ts +10 -2
  42. package/services/frontend/src/core/types/PluginRegistrar.ts +1 -1
  43. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +15 -1
  44. package/services/frontend/src/orchestrator/assets/styles/layers.css +4 -1
  45. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +267 -35
  46. package/services/frontend/src/orchestrator/components/menu/ServerStatus.vue +11 -2
  47. package/services/frontend/src/orchestrator/composables/useVueQueryObservableHelper.ts +12 -7
  48. package/services/frontend/src/orchestrator/composables/useVueWriteOperationHelper.ts +12 -6
  49. package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +2 -4
  50. package/services/frontend/src/orchestrator/i18n/de-DE.json +1 -0
  51. package/services/frontend/src/orchestrator/i18n/en-EU.json +1 -0
  52. package/services/frontend/src/orchestrator/i18n/sk.json +1 -0
  53. package/services/frontend/src/orchestrator/i18n/tl-TL.json +1 -0
  54. package/services/frontend/src/orchestrator/index.ts +7 -24
  55. package/services/frontend/src/orchestrator/lib/queryParamsSource.ts +48 -0
  56. package/services/frontend/src/orchestrator/main.ts +2 -2
  57. package/services/frontend/src/orchestrator/router/index.ts +0 -19
  58. package/services/frontend/src/orchestrator/router/registerEventHandlers.ts +29 -0
  59. package/services/frontend/src/orchestrator/router/routeStore.ts +92 -17
  60. package/services/frontend/src/orchestrator/router/routes.ts +5 -0
  61. package/services/frontend/src/orchestrator/setup/application.ts +11 -2
  62. package/services/frontend/src/orchestrator/types/DataApi.ts +19 -0
  63. package/services/frontend/src/orchestrator/types/PluginApi.ts +4 -7
  64. package/services/frontend/src/orchestrator/types/Widgets.ts +2 -0
  65. package/services/frontend/src/orchestrator/types/index.ts +1 -0
  66. package/templates/docker-compose.yml +1 -4
  67. package/templates/fs.yml +1 -1
  68. package/package/dist/index.js +0 -452
  69. package/services/backend/.yarn/install-state.gz +0 -0
  70. package/services/backend/dist/core/eventBus/index.js +0 -7
  71. package/services/backend/dist/core/pluginSystem/configGenerator/index.js +0 -346
  72. package/services/backend/dist/core/sockets/index.js +0 -95
  73. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/events/index.js +0 -31
  74. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/index.js +0 -48
  75. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/routes/index.js +0 -15
  76. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/events/index.js +0 -22
  77. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/index.js +0 -51
  78. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/routes/index.js +0 -19
  79. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/events/index.js +0 -22
  80. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/index.js +0 -48
  81. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.js +0 -19
  82. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/events/index.js +0 -31
  83. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/index.js +0 -49
  84. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/routes/index.js +0 -16
  85. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/events/index.js +0 -39
  86. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/index.js +0 -50
  87. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/routes/index.js +0 -19
  88. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/events/index.js +0 -31
  89. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/index.js +0 -51
  90. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/routes/index.js +0 -21
  91. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/events/index.js +0 -31
  92. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/index.js +0 -48
  93. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.js +0 -15
  94. package/services/backend/dist/corePlugins/raclette__core/backend/index.js +0 -34
  95. package/services/backend/dist/domains/index.js +0 -11
  96. package/services/backend/dist/domains/system/index.js +0 -11
  97. package/services/backend/dist/domains/system/routes/index.js +0 -17
  98. package/services/backend/dist/helpers/index.js +0 -14
  99. package/services/backend/dist/index.js +0 -3
  100. package/services/backend/dist/modules/authentication/index.js +0 -253
  101. package/services/backend/dist/shared/types/core/index.js +0 -8
  102. package/services/backend/dist/shared/types/dataTypes/index.js +0 -6
  103. package/services/backend/dist/shared/types/index.js +0 -8
  104. package/services/backend/dist/shared/types/plugins/index.js +0 -8
  105. package/services/backend/dist/types/index.js +0 -12
  106. package/services/backend/dist/utils/index.js +0 -2
  107. package/services/frontend/.yarn/install-state.gz +0 -0
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@raclettejs/core",
3
- "version": "0.1.34",
3
+ "version": "0.1.36-canary.0",
4
4
  "description": "racletteJS core package",
5
5
  "repository": "https://gitlab.com/raclettejs/core",
6
6
  "author": "Pacifico Digital Explorations GmbH <info@raclettejs.com>",
7
7
  "license": "AGPL-3.0-only",
8
8
  "type": "module",
9
9
  "scripts": {
10
- ":install": "yarn install",
11
10
  ":install:services": "yarn --cwd ./services/backend install && yarn --cwd ./services/frontend install",
12
11
  "build": "node build.mjs || true",
13
12
  "dev": "node watch.mjs",
@@ -17,7 +16,6 @@
17
16
  "lint": "eslint",
18
17
  "lint:grouped": "FILTER_RULE=$RULE eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --format ./dev/eslint/groupByRuleFormatter.cjs",
19
18
  "lint:fix": "eslint --fix",
20
- "prepare": "husky",
21
19
  "prepublishOnly": "yarn run build && yarn run sync",
22
20
  "publish:patch": "yarn version --patch && yarn publish",
23
21
  "sync": "node ./scripts/sync-exports.js"
@@ -30,6 +28,9 @@
30
28
  "types": "./types/index.ts",
31
29
  "files": [
32
30
  "services",
31
+ "!services/**/.yarn",
32
+ "!services/**/node_modules",
33
+ "!services/**/.yarnrc.yml",
33
34
  "bin",
34
35
  "dist",
35
36
  "types",
@@ -82,7 +83,7 @@
82
83
  "@emnapi/core": "1.9.2",
83
84
  "@emnapi/runtime": "1.9.2",
84
85
  "@eslint/js": "9.35.0",
85
- "@raclettejs/types": "^0.1.34",
86
+ "@raclettejs/types": "workspace:",
86
87
  "@types/fs-extra": "11.0.4",
87
88
  "@types/js-yaml": "4.0.9",
88
89
  "@types/node": "25.5.0",
@@ -95,7 +96,6 @@
95
96
  "eslint-plugin-prettier": "5.5.5",
96
97
  "eslint-plugin-vue": "9.33.0",
97
98
  "globals": "17.4.0",
98
- "husky": "9.1.7",
99
99
  "prettier": "3.8.1",
100
100
  "typescript": "5.9.3",
101
101
  "typescript-eslint": "8.59.2",
@@ -4,3 +4,5 @@
4
4
  **/.git
5
5
  **/.gitignore
6
6
  **/docker-compose*
7
+ **/.yarnrc.yml
8
+ **/.yarn
@@ -9,4 +9,6 @@ package.json
9
9
  yarn.lock
10
10
  .prettierrc
11
11
  eslint.config.js
12
- dist
12
+ dist
13
+ .yarnrc.yml
14
+ .yarn
@@ -48,4 +48,5 @@ export type BodyContent<T> = {
48
48
 
49
49
  export type FrontendPayload<T> = FrontendPayloadRequestData & {
50
50
  body: BodyContent<T>
51
+ workSessionId?: string
51
52
  }
@@ -1,5 +1,8 @@
1
1
  import type { FastifyInstance } from "fastify"
2
- import type { PluginOptions } from "../pluginTypes"
2
+ import type {
3
+ PluginOptions,
4
+ RegisterForFrontendGenerationOptions,
5
+ } from "../pluginTypes"
3
6
  import type {
4
7
  PluginRegistration,
5
8
  TrackedRoute,
@@ -137,17 +140,14 @@ class FrontendConfigRegistry {
137
140
  // Plugin Registration Functions (Called by Plugins)
138
141
  // ========================================
139
142
 
143
+ export type {
144
+ RegisterForFrontendGenerationOptions,
145
+ RegisterForFrontendGenerationInput,
146
+ } from "../pluginTypes"
147
+
140
148
  export const registerPluginForFrontendGeneration = (
141
149
  pluginOptions: PluginOptions,
142
- options?: {
143
- routePrefix?: string
144
- entityMapping?: Record<string, string>
145
- customConfig?: {
146
- broadcast?: boolean
147
- channels?: Record<string, string>
148
- operationMapping?: Record<string, string>
149
- }
150
- },
150
+ options?: RegisterForFrontendGenerationOptions,
151
151
  ): void => {
152
152
  const registry = FrontendConfigRegistry.getInstance()
153
153
  registry.registerPlugin(pluginOptions, options)
@@ -306,7 +306,8 @@ const parseRouteInfo = (
306
306
  .filter((segment) => segment.startsWith(":"))
307
307
  .map((param) => param.substring(1))
308
308
 
309
- const operationName = getOperationName(method, operationSegments)
309
+ const operationName =
310
+ route.config?.operationName ?? getOperationName(method, operationSegments)
310
311
 
311
312
  return {
312
313
  method,
@@ -8,7 +8,11 @@ import type {
8
8
  IntervalTaskOptions,
9
9
  } from "@/types"
10
10
  import type { CrudHandlers } from "@c/crud/crudTypes"
11
- import type { PluginFastifyInstance, PluginOptions } from "./pluginTypes"
11
+ import type {
12
+ PluginFastifyInstance,
13
+ PluginOptions,
14
+ RegisterForFrontendGenerationInput,
15
+ } from "./pluginTypes"
12
16
  import mongoose from "mongoose"
13
17
  import type EventEmitter from "@c/events/eventEmitter"
14
18
  import { type Listener } from "@c/events/eventEmitter"
@@ -204,8 +208,29 @@ export const createPluginFastifyInstance = (
204
208
  registerPluginContract(pluginOptions.key, contractData)
205
209
  },
206
210
 
207
- registerForFrontendGeneration: (options: any) => {
208
- registerPluginForFrontendGeneration(pluginOptions, options)
211
+ registerForFrontendGeneration: (
212
+ options?: RegisterForFrontendGenerationInput,
213
+ ) => {
214
+ const fg = pluginOptions._loaderInternal?.frontendGeneration
215
+ if (!fg) {
216
+ if (options === false) {
217
+ return
218
+ }
219
+ registerPluginForFrontendGeneration(
220
+ pluginOptions,
221
+ options === undefined ? {} : options,
222
+ )
223
+ return
224
+ }
225
+ if (options === false) {
226
+ fg.optedOut = true
227
+ return
228
+ }
229
+ fg.explicitCall = true
230
+ registerPluginForFrontendGeneration(
231
+ pluginOptions,
232
+ options === undefined ? {} : options,
233
+ )
209
234
  },
210
235
 
211
236
  cache: pluginCacheService,
@@ -5,6 +5,7 @@ import type { FastifyInstance } from "fastify"
5
5
  import fs from "fs"
6
6
  import path from "path"
7
7
  import { createPluginFastifyInstance } from "./pluginFastifyInstance"
8
+ import { registerPluginForFrontendGeneration } from "@c/pluginSystem/configGenerator"
8
9
  import configService from "@c/config/configService"
9
10
  import { slugify } from "@shared/helper/pluginHelper"
10
11
  import { fileURLToPath } from "url"
@@ -759,6 +760,9 @@ const registerCollectedPlugins = async (
759
760
  routePrefix,
760
761
  absolutePath: plugin.pluginPath,
761
762
  frontendAbsolutePath: plugin.frontendPath,
763
+ _loaderInternal: {
764
+ frontendGeneration: { explicitCall: false, optedOut: false },
765
+ },
762
766
  }
763
767
 
764
768
  // Dynamically import the plugin module
@@ -786,10 +790,15 @@ const registerCollectedPlugins = async (
786
790
  const customFastify = createPluginFastifyInstance(fastify, pluginOptions)
787
791
 
788
792
  // Register the plugin, passing the restricted instance and options
789
- await fastify.register(() => {
790
- return pluginFunction(customFastify, pluginOptions)
793
+ await fastify.register(async () => {
794
+ await pluginFunction(customFastify, pluginOptions)
791
795
  })
792
796
 
797
+ const fg = pluginOptions._loaderInternal?.frontendGeneration
798
+ if (fg && !fg.optedOut && !fg.explicitCall) {
799
+ registerPluginForFrontendGeneration(pluginOptions, {})
800
+ }
801
+
793
802
  logPluginSuccess(
794
803
  fastify,
795
804
  plugin.type,
@@ -20,6 +20,18 @@ export enum PluginType {
20
20
  APP = "app",
21
21
  }
22
22
 
23
+ /**
24
+ * Internal state the plugin loader attaches to `PluginOptions` while a backend
25
+ * plugin boots. Extend with additional nested sections as new loader features
26
+ * need post-plugin hooks.
27
+ */
28
+ export type PluginLoaderInternalState = {
29
+ frontendGeneration: {
30
+ explicitCall: boolean
31
+ optedOut: boolean
32
+ }
33
+ }
34
+
23
35
  export type PluginOptions = {
24
36
  key: string
25
37
  pluginName: string
@@ -33,8 +45,32 @@ export type PluginOptions = {
33
45
  registerEvents?: (io: SocketServer, eventbus: any) => void
34
46
  createRooms?: (io: SocketServer) => void
35
47
  }
48
+ /**
49
+ * Loader-owned mutable bookkeeping (not part of the public plugin API).
50
+ * Plugin code should ignore; the loader reads this after `index.ts` returns.
51
+ */
52
+ _loaderInternal?: PluginLoaderInternalState
53
+ }
54
+
55
+ /** Options when a plugin customizes frontend generation metadata. */
56
+ export type RegisterForFrontendGenerationOptions = {
57
+ routePrefix?: string
58
+ entityMapping?: Record<string, string>
59
+ customConfig?: {
60
+ broadcast?: boolean
61
+ channels?: Record<string, string>
62
+ operationMapping?: Record<string, string>
63
+ }
36
64
  }
37
65
 
66
+ /**
67
+ * Argument to `fastify.registerForFrontendGeneration`.
68
+ * Pass `false` to opt out of frontend generation registration for this plugin.
69
+ */
70
+ export type RegisterForFrontendGenerationInput =
71
+ | RegisterForFrontendGenerationOptions
72
+ | false
73
+
38
74
  export type PluginFastifyInstance = {
39
75
  /* ROUTES */
40
76
  register: (
@@ -87,7 +123,9 @@ export type PluginFastifyInstance = {
87
123
  eventbus: EventEmitter
88
124
 
89
125
  registerPluginContract: (contractData: PluginContract) => void
90
- registerForFrontendGeneration: (options: any) => any
126
+ registerForFrontendGeneration: (
127
+ options?: RegisterForFrontendGenerationInput,
128
+ ) => void
91
129
 
92
130
  fastify: FastifyInstance
93
131
  custom: Record<any, any>
@@ -9,7 +9,7 @@ export const createSystemEventHandlers = (fastify: FastifyInstance) => {
9
9
  // TODO rework this to handle spaces instead of project and in the future document rooms -> y.js?
10
10
  // Is this even used? SocketManager also has wss.emit
11
11
  fastify.log.debug(
12
- `[Socket] sendToChannel ${key} to room ${payload.project} and ${payload.workSessionId}`,
12
+ `[Socket] sendToChannel ${key} to room ${payload.project}${payload.workSessionId ? ' and workSession ' + payload.workSessionId : ''}`,
13
13
  )
14
14
  if (payload.project) {
15
15
  wss.in(payload.project).emit(key, payload)
@@ -22,15 +22,15 @@ export const createSystemEventHandlers = (fastify: FastifyInstance) => {
22
22
  }
23
23
 
24
24
  return {
25
- broadcastDataUpdatedHandler: (payload: FrontendPayload<unknown>): void => {
25
+ broadcastDataUpdated: (payload: FrontendPayload<unknown>): void => {
26
26
  fastify.log.info(`[Socket] Push onData: ${payload.appSessionId}`)
27
27
  sendToChannel(payload, "data")
28
28
  },
29
- broadcastDataCreatedHandler: (payload: FrontendPayload<unknown>): void => {
29
+ broadcastDataCreated: (payload: FrontendPayload<unknown>): void => {
30
30
  fastify.log.info(`[Socket] Push onCreate: ${payload.appSessionId}`)
31
31
  sendToChannel(payload, "dataCreated")
32
32
  },
33
- broadcastDataDeletedHandler: (payload: FrontendPayload<unknown>): void => {
33
+ broadcastDataDeleted: (payload: FrontendPayload<unknown>): void => {
34
34
  fastify.log.info(`[Socket] Push onDelete: ${payload.appSessionId}`)
35
35
  sendToChannel(payload, "dataDeleted")
36
36
  },
@@ -8,8 +8,7 @@ import { buildWorkSessionJoinPayload } from "./workSessionJoinPayload"
8
8
 
9
9
  interface JoinPayload {
10
10
  roomId?: string
11
- token?: string
12
- tenantId?: string
11
+ bootstrap?: boolean
13
12
  }
14
13
 
15
14
  interface ProjectResponse {
@@ -29,7 +28,25 @@ const isSocketAuthenticated = (
29
28
  }
30
29
 
31
30
  /**
32
- * Load project by ID or get main project
31
+ * Guard for explicit room joins. Server-assigned readMain joins skip this.
32
+ */
33
+ const assertCanJoinProject = async (
34
+ fastify: FastifyInstance,
35
+ socket: Socket,
36
+ spaceId: string,
37
+ ): Promise<ProjectResponse> => {
38
+ const project = await loadProject(fastify, spaceId)
39
+ if (!project) {
40
+ throw new Error("Space not found")
41
+ }
42
+
43
+ // TODO: here we add the guard if a user is allowed to join a specific space or not
44
+
45
+ throw new Error("Not allowed to join this space")
46
+ }
47
+
48
+ /**
49
+ * Load project by ID or assign main project when roomId is omitted
33
50
  */
34
51
  const loadProject = async (
35
52
  fastify: FastifyInstance,
@@ -379,34 +396,66 @@ const handleAuthenticatedUserJoin = async (
379
396
  socket: Socket,
380
397
  payload: JoinPayload,
381
398
  ): Promise<void> => {
382
- const { roomId: spaceId } = payload
383
- const project = await loadProject(fastify, spaceId)
384
-
385
- if (!project) {
386
- socket.emit("error", {
387
- error: {
388
- msg: spaceId
389
- ? "Space not found or access denied"
390
- : "No suiting space found. Something seems wrongly configured!",
391
- },
392
- })
393
- return
399
+ const explicitSpaceId = payload.roomId
400
+ const bootstrap = payload.bootstrap ?? !explicitSpaceId
401
+ let spaceId = explicitSpaceId
402
+ let project: ProjectResponse | null = null
403
+
404
+ if (explicitSpaceId) {
405
+ try {
406
+ project = await assertCanJoinProject(fastify, socket, explicitSpaceId)
407
+ spaceId = explicitSpaceId
408
+ } catch (error) {
409
+ const message =
410
+ error instanceof Error ? error.message : "Access denied"
411
+ const code = message === "Space not found" ? "NOT_FOUND" : "FORBIDDEN"
412
+ socket.emit("joinFailed", {
413
+ roomId: explicitSpaceId,
414
+ code,
415
+ message,
416
+ })
417
+ return
418
+ }
419
+ } else {
420
+ // per default we let every user join the default space for the app
421
+ project = await loadProject(fastify)
422
+ if (!project) {
423
+ socket.emit("joinFailed", {
424
+ code: "NOT_FOUND",
425
+ message: "No default space found. Something seems wrongly configured!",
426
+ })
427
+ return
428
+ }
429
+ spaceId = project._id
394
430
  }
395
431
 
396
- socket.join(project._id)
397
- socket.data = socket.data || {}
398
- socket.data.project = project._id
432
+ await socket.join(spaceId)
433
+ if (!socket.data.projects) {
434
+ socket.data.projects = new Set<string>()
435
+ }
436
+ socket.data.projects.add(spaceId)
399
437
 
400
- // Join user's owned work sessions
401
- const decodedToken = socket.decoded_token as any
438
+ const decodedToken = socket.decoded_token as { _id?: string }
402
439
  const userId = decodedToken?._id
403
- if (userId) {
440
+ if (bootstrap && userId) {
404
441
  await joinOwnedWorkSessions(fastify, socket, userId)
405
442
  }
406
443
 
407
- const responseData = await buildAuthenticatedUserResponse(fastify, project)
408
- socket.emit("joinConfirmation", responseData)
409
- fastify.log.info(`[Socket] Socket ${socket.id} joined room ${project._id}`)
444
+ const responseData =
445
+ bootstrap && project
446
+ ? await buildAuthenticatedUserResponse(fastify, project)
447
+ : project
448
+ ? { project }
449
+ : {}
450
+
451
+ socket.emit("joinConfirmation", {
452
+ ...responseData,
453
+ roomId: spaceId,
454
+ bootstrap,
455
+ })
456
+ fastify.log.info(
457
+ `[Socket] Socket ${socket.id} joined room ${spaceId}, rooms: ${[...socket.rooms].join(", ")}`,
458
+ )
410
459
  }
411
460
 
412
461
  /**
@@ -418,20 +467,18 @@ const registerSocketEvents = (fastify: FastifyInstance): void => {
418
467
  const requiresAuth = configService.isAuthRequired()
419
468
 
420
469
  wss.on("connection", (socket: Socket) => {
421
- const isAuthenticated = isSocketAuthenticated(socket, requiresAuth)
422
-
423
470
  fastify.log.info(`[Socket] Connected ${socket.id}`)
424
471
  socket.emit("socketEstablished", { id: socket.id })
425
472
 
426
- if (!isAuthenticated && requiresAuth) {
473
+ if (!isSocketAuthenticated(socket, requiresAuth) && requiresAuth) {
427
474
  fastify.log.warn(`[Socket] Unauthenticated connection ${socket.id}`)
428
475
  socket.emit("authenticationRequired", {
429
476
  message: "Authentication required",
430
477
  })
431
478
  }
432
479
 
433
- socket.on("join", async (payload: JoinPayload) => {
434
- if (requiresAuth && !isAuthenticated) {
480
+ socket.on("join", async (payload: JoinPayload = {}) => {
481
+ if (requiresAuth && !isSocketAuthenticated(socket, requiresAuth)) {
435
482
  socket.emit("authenticationFailed", {
436
483
  message: "Authentication required to join room",
437
484
  })
@@ -451,6 +498,7 @@ const registerSocketEvents = (fastify: FastifyInstance): void => {
451
498
 
452
499
  socket.on("leave", (room: string) => {
453
500
  socket.leave(room)
501
+ socket.data.projects?.delete(room)
454
502
  socket.emit("leaveConfirmation", room)
455
503
  wss.in(room).emit("memberLeft", {
456
504
  room,
@@ -465,9 +513,9 @@ const registerSocketEvents = (fastify: FastifyInstance): void => {
465
513
 
466
514
  // Register system event handlers
467
515
  const systemEvents = {
468
- broadcastDataUpdated: eventbusHandler.broadcastDataUpdatedHandler,
469
- broadcastDataCreated: eventbusHandler.broadcastDataCreatedHandler,
470
- broadcastDataDeleted: eventbusHandler.broadcastDataDeletedHandler,
516
+ broadcastDataUpdated: eventbusHandler.broadcastDataUpdated,
517
+ broadcastDataCreated: eventbusHandler.broadcastDataCreated,
518
+ broadcastDataDeleted: eventbusHandler.broadcastDataDeleted,
471
519
  broadcastTagCreated: eventbusHandler.broadcastTagCreated,
472
520
  broadcastTagDeleted: eventbusHandler.broadcastTagDeleted,
473
521
  broadcastUserCreated: eventbusHandler.broadcastUserCreated,
@@ -2,6 +2,12 @@ import type { DefaultEventsMap, EventsMap } from "socket.io/dist/typed-events"
2
2
  import type { VerifyPayloadType } from "@fastify/jwt"
3
3
 
4
4
  declare module "socket.io" {
5
+ interface SocketData {
6
+ projects?: Set<string>
7
+ ownedSessions?: string[]
8
+ session?: string
9
+ }
10
+
5
11
  interface Socket<
6
12
  ListenEvents extends EventsMap = DefaultEventsMap,
7
13
  EmitEvents extends EventsMap = ListenEvents,
@@ -81,7 +81,10 @@ export const generateSharedSchemas = async (sharedDir: string) => {
81
81
 
82
82
  // Generate index files
83
83
  await generateCoreTypesIndex(sharedDir)
84
- await generatePluginTypesIndex(sharedDir)
84
+ if (process.env.RACLETTE_CORE_ABSOLUTE_PATH?.includes("node_modules")) {
85
+ // only generate this for non-linked core to prevent mixup of app content in code
86
+ await generatePluginTypesIndex(sharedDir)
87
+ }
85
88
  await generateMainTypesIndex(sharedDir)
86
89
  }
87
90
 
@@ -268,7 +271,10 @@ const generateCoreTypesIndex = async (sharedDir: string) => {
268
271
  .join("\n")
269
272
 
270
273
  // Write index file
271
- await fs.writeFile(path.join(coreDir, "index.ts"), generateTypesFileContent(exportStatements))
274
+ await fs.writeFile(
275
+ path.join(coreDir, "index.ts"),
276
+ generateTypesFileContent(exportStatements),
277
+ )
272
278
  }
273
279
 
274
280
  // Helper function to generate the plugins index.ts
@@ -302,7 +308,8 @@ const generatePluginTypesIndex = async (sharedDir: string) => {
302
308
 
303
309
  // Helper function to generate the main types/index.ts
304
310
  const generateMainTypesIndex = async (sharedDir: string) => {
305
- const content = generateTypesFileContent(`// Import and re-export all core types
311
+ const content =
312
+ generateTypesFileContent(`// Import and re-export all core types
306
313
  export type * from './core'
307
314
 
308
315
  // Import and re-export all plugin types
@@ -150,7 +150,7 @@ export class ProjectService {
150
150
  }
151
151
 
152
152
  /**
153
- * Core function to get a specific project (returns raw data)
153
+ * Core function to the main project (used until spaces are coming)
154
154
  */
155
155
  async _readMainProject(fastify: PluginFastifyInstance): Promise<ProjectType> {
156
156
  try {
@@ -42,8 +42,6 @@ const racletteCorePlugin = async (
42
42
 
43
43
  // Register the complete contract
44
44
  fastify.registerPluginContract(pluginContract)
45
-
46
- fastify.registerForFrontendGeneration({})
47
45
  }
48
46
 
49
47
  export default racletteCorePlugin
@@ -4,6 +4,11 @@ import fastifyPlugin from "fastify-plugin"
4
4
  import type { User } from "@shared/types"
5
5
  import configService from "@c/config/configService"
6
6
  import type { WorkSessionDoc } from "@/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema"
7
+ import {
8
+ ADMIN_SYSTEM_TAG_QUERY,
9
+ type AdminTaggableUser,
10
+ userHasAdminTag,
11
+ } from "@shared/helper/adminTag"
7
12
  import { ForbiddenError } from "@/utils/errors"
8
13
 
9
14
  const WORK_SESSION_COLLECTION = "raclette__core-worksession"
@@ -87,25 +92,20 @@ export const authenticatedOrWorksession = async (
87
92
  const { _id } = req.user
88
93
  const adminTag = await fastify.database
89
94
  .collection("raclette__core-tag")
90
- .findOne({ title: "ADMIN", locked: true, type: "system" })
91
- let isAdmin = false
95
+ .findOne(ADMIN_SYSTEM_TAG_QUERY)
92
96
  const user = await fastify.database
93
97
  .collection("raclette__core-user")
94
98
  .findOne({ _id }, { projection: { password: 0, resetKey: 0 } })
95
- if (user && user.tags && adminTag) {
96
- user.tags.forEach((tag: string) => {
97
- if (adminTag._id === tag) isAdmin = true
98
- })
99
+ if (!user) {
100
+ fastify.log.error("User not found")
101
+ return res.status(401).send({ message: "User not found" })
99
102
  }
103
+ const isAdmin = userHasAdminTag(user as AdminTaggableUser, adminTag)
100
104
  if (isAdminTagRequired && !isAdmin) {
101
105
  fastify.log.error("User not allowed in workbench")
102
106
  return res.status(403).send({ message: "User not allowed in workbench" })
103
107
  }
104
108
  if (isAdmin) user.isAdmin = true
105
- if (!user) {
106
- fastify.log.error("User not found")
107
- return res.status(401).send({ message: "User not found" })
108
- }
109
109
  const account = await fastify.database
110
110
  .collection("raclette__core-account")
111
111
  .findOne({ _id: user.account })
@@ -244,24 +244,21 @@ export const authenticate = async (
244
244
  const { _id } = req.user
245
245
  const adminTag = await fastify.database
246
246
  .collection("raclette__core-tag")
247
- .findOne({ title: "ADMIN", locked: true, type: "system" })
247
+ .findOne(ADMIN_SYSTEM_TAG_QUERY)
248
248
  // IMPORTANT
249
249
  // due to some very weird behaviour, mongoose always returns empty results
250
250
  // so we are using mongodb directly until we fix this
251
- let isAdmin = false
252
251
  const user = await fastify.database
253
252
  .collection("raclette__core-user")
254
253
  .findOne(
255
254
  { _id },
256
255
  { projection: { password: 0, resetKey: 0 } }, // Exclude sensitive fields
257
256
  )
258
- if (user && user.tags && adminTag) {
259
- user.tags.forEach((tag) => {
260
- if (adminTag._id === tag) {
261
- isAdmin = true
262
- }
263
- })
257
+ if (!user) {
258
+ fastify.log.error("User not found")
259
+ throw new Error("User not found")
264
260
  }
261
+ const isAdmin = userHasAdminTag(user as AdminTaggableUser, adminTag)
265
262
  if (isAdminTagRequired && !isAdmin) {
266
263
  fastify.log.error("User not allowed in workbench")
267
264
  throw new Error("User not allowed in workbench")
@@ -270,11 +267,6 @@ export const authenticate = async (
270
267
  user.isAdmin = true
271
268
  }
272
269
 
273
- if (!user) {
274
- fastify.log.error("User not found")
275
- throw new Error("User not found")
276
- }
277
-
278
270
  const account = await fastify.database
279
271
  .collection("raclette__core-account")
280
272
  .findOne({ _id: user.account })
@@ -0,0 +1,34 @@
1
+ export const ADMIN_SYSTEM_TAG_QUERY = {
2
+ title: "ADMIN",
3
+ locked: true,
4
+ type: "system",
5
+ } as const
6
+
7
+ export type SystemAdminTagLike = {
8
+ title?: string
9
+ locked?: boolean
10
+ type?: string
11
+ }
12
+
13
+ export type AdminTaggableUser = {
14
+ tags?: string[]
15
+ }
16
+
17
+ export function isSystemAdminTag(
18
+ tag: SystemAdminTagLike | null | undefined,
19
+ ): boolean {
20
+ if (!tag) return false
21
+ return (
22
+ tag.title === ADMIN_SYSTEM_TAG_QUERY.title &&
23
+ tag.locked === ADMIN_SYSTEM_TAG_QUERY.locked &&
24
+ tag.type === ADMIN_SYSTEM_TAG_QUERY.type
25
+ )
26
+ }
27
+
28
+ export function userHasAdminTag(
29
+ user: AdminTaggableUser | null | undefined,
30
+ adminTag: { _id: string } | null | undefined,
31
+ ): boolean {
32
+ if (!user?.tags?.length || !adminTag?._id) return false
33
+ return user.tags.includes(adminTag._id)
34
+ }
@@ -1,9 +1 @@
1
- /**
2
- * ⚠️ WARNING: THIS FILE IS AUTO-GENERATED ⚠️
3
- *
4
- * This file is generated by the type generation system.
5
- * Any manual changes will be overwritten on next server start and should always be committed.
6
- *
7
- */
8
-
9
- export type * from './pacifico__openstack-billing-dashboard'
1
+ export type {}
@@ -19,7 +19,7 @@ export const createRequestParams = (
19
19
  appRequestCode: req.headers["app-request-code"] ?? "",
20
20
  requestId: req.id ?? "",
21
21
  type: "",
22
- broadcast: req.headers["app-broadcast-channels"] ?? false,
22
+ broadcast: req.headers["app-broadcast-channels"]?.length ? true : false,
23
23
  project: projectId,
24
24
  userId: req.user?.id,
25
25
  options: {},