@raclettejs/core 0.1.19 → 0.1.20

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 (132) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/cli.js +1 -1
  3. package/dev/README.md +4 -0
  4. package/dist/cli.js +135 -124
  5. package/dist/cli.js.map +4 -4
  6. package/dist/index.js +9 -8
  7. package/dist/index.js.map +4 -4
  8. package/package.json +9 -5
  9. package/services/backend/delay-api-debugger.sh +0 -0
  10. package/services/backend/package.json +1 -0
  11. package/services/backend/src/app.ts +4 -0
  12. package/services/backend/src/core/config/configService.ts +10 -1
  13. package/services/backend/src/core/events/eventEmitter.ts +32 -7
  14. package/services/backend/src/core/http/request-params.plugin.ts +2 -2
  15. package/services/backend/src/core/pluginSystem/pluginFastifyInstance.ts +4 -1
  16. package/services/backend/src/core/pluginSystem/pluginTypes.ts +3 -1
  17. package/services/backend/src/core/sockets/createSystemEventHandlers.ts +7 -0
  18. package/services/backend/src/core/sockets/index.ts +61 -13
  19. package/services/backend/src/core/sockets/socketManager.ts +452 -105
  20. package/services/backend/src/core/sockets/socketTypes.ts +13 -1
  21. package/services/backend/src/core/sockets/workSessionJoinPayload.ts +106 -0
  22. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.get.ts +3 -1
  23. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.getAll.ts +3 -1
  24. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.hardDelete.ts +16 -19
  25. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.patch.ts +3 -1
  26. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.post.ts +3 -1
  27. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.remove.ts +3 -1
  28. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +2 -3
  29. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.get.ts +3 -1
  30. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.getAll.ts +3 -1
  31. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.hardDelete.ts +16 -19
  32. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.patch.ts +3 -1
  33. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.bulk.ts +3 -1
  34. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.ts +3 -1
  35. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.remove.ts +3 -1
  36. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.restore.ts +12 -15
  37. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.service.ts +2 -3
  38. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.get.ts +3 -1
  39. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.getAll.ts +4 -2
  40. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.hardDelete.ts +12 -17
  41. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.patch.ts +3 -1
  42. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.bulk.ts +3 -1
  43. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.ts +3 -1
  44. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.remove.ts +3 -1
  45. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.restore.ts +12 -15
  46. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.service.ts +1 -0
  47. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.get.ts +3 -1
  48. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.getAll.ts +4 -2
  49. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.hardDelete.ts +3 -1
  50. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.patch.ts +3 -1
  51. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.post.ts +4 -2
  52. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.remove.ts +3 -1
  53. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.get.ts +1 -0
  54. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.getAll.ts +1 -0
  55. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.hardDelete.ts +10 -15
  56. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.patch.ts +3 -1
  57. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.bulk.ts +3 -1
  58. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.ts +3 -1
  59. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.remove.ts +3 -1
  60. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.restore.ts +12 -15
  61. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +1 -2
  62. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.get.ts +1 -0
  63. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.getAll.ts +1 -0
  64. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.hardDelete.ts +10 -15
  65. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patch.ts +4 -6
  66. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchLastProject.ts +4 -2
  67. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.post.ts +3 -1
  68. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.remove.ts +3 -4
  69. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.restore.ts +12 -15
  70. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/events/index.ts +45 -0
  71. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/crud.ts +12 -0
  72. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/payload.ts +21 -0
  73. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/index.ts +55 -0
  74. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.ts +19 -0
  75. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.get.ts +53 -0
  76. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.getAll.ts +39 -0
  77. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.hardDelete.ts +53 -0
  78. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.patch.ts +58 -0
  79. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.post.ts +53 -0
  80. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.remove.ts +52 -0
  81. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.model.ts +65 -0
  82. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema.ts +125 -0
  83. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.service.ts +351 -0
  84. package/services/backend/src/corePlugins/raclette__core/backend/index.ts +2 -0
  85. package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +159 -24
  86. package/services/backend/src/modules/authentication/index.ts +255 -4
  87. package/services/backend/src/modules/hooks/rate-limiter.ts +21 -0
  88. package/services/backend/src/types/custom-fastify.d.ts +17 -0
  89. package/services/backend/src/utils/request.utils.ts +4 -1
  90. package/services/backend/types/index.d.ts +15 -1
  91. package/services/backend/yarn.lock +13 -4
  92. package/services/frontend/index.html +1 -1
  93. package/services/frontend/provision/entrypoint.sh +0 -0
  94. package/services/frontend/src/core/lib/configService.ts +1 -1
  95. package/services/frontend/src/core/lib/dataHelper.ts +1 -1
  96. package/services/frontend/src/core/lib/eggs/snow.ts +49 -20
  97. package/services/frontend/src/core/lib/helpers/index.ts +1 -0
  98. package/services/frontend/src/core/lib/helpers/removeEmpty.ts +69 -0
  99. package/services/frontend/src/core/lib/httpClient.ts +19 -0
  100. package/services/frontend/src/core/main.ts +20 -10
  101. package/services/frontend/src/core/setup/socket.ts +4 -3
  102. package/services/frontend/src/core/setup/socketClient.ts +31 -3
  103. package/services/frontend/src/core/setup/socketEvents.ts +92 -8
  104. package/services/frontend/src/core/store/index.ts +5 -1
  105. package/services/frontend/src/core/store/reducers/compositions/index.ts +2 -2
  106. package/services/frontend/src/core/store/reducers/interactionLinks/index.ts +1 -1
  107. package/services/frontend/src/core/store/reducers/widgets/reducers.ts +1 -2
  108. package/services/frontend/src/core/store/types/index.ts +0 -1
  109. package/services/frontend/src/core/types/PluginRegistrar.ts +8 -6
  110. package/services/frontend/src/core/types/Store.ts +1 -0
  111. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +68 -8
  112. package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +17 -5
  113. package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
  114. package/services/frontend/src/orchestrator/composables/index.ts +1 -0
  115. package/services/frontend/src/orchestrator/composables/usePluginApi.ts +0 -3
  116. package/services/frontend/src/orchestrator/composables/useRouteState.ts +23 -4
  117. package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +166 -0
  118. package/services/frontend/src/orchestrator/helpers/index.ts +1 -0
  119. package/services/frontend/src/orchestrator/i18n/de-DE.json +1 -1
  120. package/services/frontend/src/orchestrator/i18n/en-EU.json +1 -1
  121. package/services/frontend/src/orchestrator/i18n/sk.json +1 -1
  122. package/services/frontend/src/orchestrator/index.ts +34 -4
  123. package/services/frontend/src/orchestrator/router/routeParserHelper.ts +106 -1
  124. package/services/frontend/src/orchestrator/types/Widgets.ts +2 -0
  125. package/services/frontend/types.ts +1 -0
  126. package/src/README.md +404 -0
  127. package/src/types.ts +19 -5
  128. package/templates/README.md +0 -0
  129. package/types/index.ts +18 -4
  130. package/services/backend/.gitignore +0 -17
  131. package/services/frontend/.gitignore +0 -35
  132. package/yarn.lock +0 -2937
@@ -3,6 +3,214 @@ import jwt from "@fastify/jwt"
3
3
  import fastifyPlugin from "fastify-plugin"
4
4
  import type { User } from "@shared/types"
5
5
  import configService from "@c/config/configService"
6
+ import type { WorkSessionDoc } from "@/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema"
7
+ import { ForbiddenError } from "@/utils/errors"
8
+
9
+ const WORK_SESSION_COLLECTION = "raclette__core-worksession"
10
+ const WORK_SESSION_HEADER = "x-work-session-id"
11
+
12
+ /**
13
+ * Middleware: allow either authenticated user (JWT) or valid work session (X-Work-Session-Id).
14
+ * When using this, the route states that it handles permission checks itself (e.g. compare req.workSession with query).
15
+ * - If JWT present and valid: sets req.user (normal auth).
16
+ * - If no/invalid JWT but X-Work-Session-Id present: loads work session, validates (active, not expired),
17
+ * optionally checks that workSession.pluginData includes the given pluginKey, then sets req.workSession and req.isWorkSession.
18
+ */
19
+ export const authenticatedOrWorksession = async (
20
+ req: FastifyRequest,
21
+ res: FastifyReply,
22
+ fastify: FastifyInstance,
23
+ pluginKey: string,
24
+ ) => {
25
+ fastify.log.debug(
26
+ `[Auth] authenticatedOrWorksession: ${pluginKey} ${req.method} ${req.url}`,
27
+ )
28
+
29
+ const { requireAuthentication } = configService.getConfig()?.global ?? {
30
+ requireAuthentication: true,
31
+ }
32
+ const isAdminTagRequired = configService.isAdminTagRequired()
33
+
34
+ if (!requireAuthentication) {
35
+ const mockUser: Partial<User> = { _id: "SYSTEM" }
36
+ req.user = mockUser as User
37
+ return
38
+ }
39
+
40
+ // 1) Try JWT first
41
+ try {
42
+ await req.jwtVerify()
43
+ const payload = req.user as {
44
+ type?: string
45
+ sessionId?: string
46
+ _id?: string
47
+ }
48
+ fastify.log.debug(
49
+ `[Auth] JWT verified, payload type: ${payload.type || "user"}, sessionId: ${payload.sessionId || "none"}, _id: ${payload._id || "none"}`,
50
+ )
51
+
52
+ if (payload.type === "workSession" && payload.sessionId) {
53
+ fastify.log.info(
54
+ `[Auth] Work session JWT detected, sessionId: ${payload.sessionId}, pluginKey: ${pluginKey}`,
55
+ )
56
+ // Work session JWT: load session and validate
57
+ const workSession = await loadAndValidateWorkSession(
58
+ fastify,
59
+ payload.sessionId,
60
+ pluginKey,
61
+ )
62
+ if (!workSession) {
63
+ fastify.log.warn(
64
+ `[Auth] Work session validation failed for sessionId: ${payload.sessionId}`,
65
+ )
66
+ return res
67
+ .status(401)
68
+ .send({ message: "Invalid or expired work session" })
69
+ }
70
+ fastify.log.info(
71
+ `[Auth] Work session authenticated: sessionId=${payload.sessionId}, pluginKey=${pluginKey}`,
72
+ )
73
+ req.workSession = workSession
74
+ req.isWorkSession = true
75
+
76
+ // Set projectId from workSession.project if available
77
+ if (workSession.project) {
78
+ req.projectId = workSession.project
79
+ fastify.log.debug(`[Auth] Work session projectId: ${req.projectId}`)
80
+ }
81
+ return
82
+ }
83
+ // Normal user JWT: fetch user and set req.user
84
+ fastify.log.debug(
85
+ `[Auth] Normal user JWT, fetching user: ${(req.user as any)?._id}`,
86
+ )
87
+ const { _id } = req.user
88
+ const adminTag = await fastify.database
89
+ .collection("raclette__core-tag")
90
+ .findOne({ title: "ADMIN", locked: true, type: "system" })
91
+ let isAdmin = false
92
+ const user = await fastify.database
93
+ .collection("raclette__core-user")
94
+ .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
+ }
100
+ if (isAdminTagRequired && !isAdmin) {
101
+ fastify.log.error("User not allowed in workbench")
102
+ return res.status(403).send({ message: "User not allowed in workbench" })
103
+ }
104
+ 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
+ const account = await fastify.database
110
+ .collection("raclette__core-account")
111
+ .findOne({ _id: user.account })
112
+ user.account = account
113
+ req.user = user
114
+ // Set projectId from user's lastProjectId (for requestData.project in payload wrappers)
115
+ if (user.ux?.lastProjectId) {
116
+ req.projectId = user.ux.lastProjectId
117
+ fastify.log.debug(`[Auth] User projectId: ${req.projectId}`)
118
+ }
119
+ return
120
+ } catch (error: any) {
121
+ // JWT missing or invalid: try work session via header
122
+ fastify.log.debug(
123
+ `[Auth] JWT verification failed: ${error.message}, trying work session header`,
124
+ )
125
+ }
126
+
127
+ // 2) Try X-Work-Session-Id header
128
+ const sessionId = req.headers[WORK_SESSION_HEADER] as string | undefined
129
+ if (sessionId) {
130
+ const workSession = await loadAndValidateWorkSession(
131
+ fastify,
132
+ sessionId,
133
+ pluginKey,
134
+ )
135
+ if (workSession) {
136
+ req.workSession = workSession
137
+ req.isWorkSession = true
138
+
139
+ // Set projectId from workSession.project if available
140
+ // workaround for how projects/spaces are currently handled - tbd
141
+ if (workSession.project) {
142
+ req.projectId = workSession.project
143
+ }
144
+ return
145
+ } else {
146
+ fastify.log.warn(
147
+ `[Auth] Work session validation failed for header sessionId: ${sessionId}`,
148
+ )
149
+ }
150
+ }
151
+
152
+ return res.status(401).send({ message: "Authentication required" })
153
+ }
154
+
155
+ async function loadAndValidateWorkSession(
156
+ fastify: FastifyInstance,
157
+ sessionId: string,
158
+ pluginKey: string,
159
+ ): Promise<WorkSessionDoc | null> {
160
+ fastify.log.debug(
161
+ `[WorkSession Auth] Loading and validating work session: sessionId=${sessionId}, pluginKey=${pluginKey}`,
162
+ )
163
+
164
+ if (!sessionId || typeof sessionId !== "string") {
165
+ fastify.log.warn(`[WorkSession Auth] Invalid sessionId: ${sessionId}`)
166
+ return null
167
+ }
168
+
169
+ fastify.log.debug(`[WorkSession Auth] Querying database for work session`)
170
+ const doc = await fastify.database
171
+ .collection(WORK_SESSION_COLLECTION)
172
+ .findOne({ _id: sessionId })
173
+
174
+ if (!doc) {
175
+ fastify.log.warn(`[WorkSession Auth] Work session not found: ${sessionId}`)
176
+ return null
177
+ }
178
+
179
+ if (!doc.isActive) {
180
+ fastify.log.warn(
181
+ `[WorkSession Auth] Work session is not active: ${sessionId}`,
182
+ )
183
+ return null
184
+ }
185
+
186
+ const expiry = doc.expiryDate ? new Date(doc.expiryDate) : null
187
+ if (expiry && expiry.getTime() < Date.now()) {
188
+ fastify.log.warn(
189
+ `[WorkSession Auth] Work session expired: ${sessionId}, expiryDate: ${expiry.toISOString()}`,
190
+ )
191
+ return null
192
+ }
193
+
194
+ // Check that this plugin is part of the work session (pluginData keys are "pluginKey:widgetName")
195
+ const pluginData = doc.pluginData || {}
196
+ const pluginKeys = Object.keys(pluginData)
197
+
198
+ const hasPlugin =
199
+ pluginKeys.some((k) => k.startsWith(pluginKey + ":")) ||
200
+ Object.prototype.hasOwnProperty.call(pluginData, pluginKey)
201
+
202
+ if (!hasPlugin) {
203
+ fastify.log.warn(
204
+ `[WorkSession Auth] Plugin ${pluginKey} not found in work session pluginData: ${pluginKeys.join(", ")}`,
205
+ )
206
+ return null
207
+ }
208
+
209
+ fastify.log.info(
210
+ `[WorkSession Auth] Work session validated successfully: sessionId=${sessionId}, pluginKey=${pluginKey}`,
211
+ )
212
+ return doc as WorkSessionDoc
213
+ }
6
214
 
7
215
  export const authenticate = async (
8
216
  req: FastifyRequest,
@@ -20,12 +228,18 @@ export const authenticate = async (
20
228
  _id: "SYSTEM",
21
229
  }
22
230
 
23
- req.user = mockUser
231
+ req.user = mockUser as User
24
232
  return
25
233
  }
26
234
 
27
235
  try {
28
236
  await req.jwtVerify()
237
+ const payload = req.user as { type?: string }
238
+ if (payload.type === "workSession") {
239
+ return res
240
+ .status(401)
241
+ .send({ message: "Work session token not allowed on this route" })
242
+ }
29
243
  // fetch current user data from the db
30
244
  const { _id } = req.user
31
245
  const adminTag = await fastify.database
@@ -73,17 +287,42 @@ export const authenticate = async (
73
287
  }
74
288
  }
75
289
  }
76
-
290
+ export const checkPermissions = async (
291
+ req: FastifyRequest,
292
+ res: FastifyReply,
293
+ fastify: FastifyInstance,
294
+ permissions: Array<String>,
295
+ ) => {
296
+ {
297
+ try {
298
+ if (!permissions.length) {
299
+ fastify.log.error("No permissions given for permission check")
300
+ throw new Error("No Permissions")
301
+ }
302
+ // TODO implement proper Permission lookup here
303
+ permissions.forEach((permissionStr) => {
304
+ if (permissionStr === "user.isAdmin") {
305
+ if (!req.user.isAdmin) {
306
+ throw new ForbiddenError("User not permitted to action")
307
+ }
308
+ }
309
+ })
310
+ } catch (error: any) {
311
+ fastify.log.error(error.message)
312
+ return res.send(error.message)
313
+ }
314
+ }
315
+ }
77
316
  export default fastifyPlugin(async (fastify: FastifyInstance, opts = {}) => {
78
317
  // provide some sensible options
79
318
  const defaults = {
80
319
  secret: false,
81
320
  decode: { complete: true },
82
321
  sign: {
83
- iss: "api.projectjournal.pacifico.info",
322
+ iss: "api.raclettejs.info",
84
323
  expiresIn: "30d",
85
324
  },
86
- verify: { allowedIss: "api.projectjournal.pacifico.info" },
325
+ verify: { allowedIss: "api.raclettejs.info" },
87
326
  }
88
327
 
89
328
  // merge proved opts with defaults
@@ -96,4 +335,16 @@ export default fastifyPlugin(async (fastify: FastifyInstance, opts = {}) => {
96
335
  fastify.decorate("authenticate", (req, res) => {
97
336
  return authenticate(req, res, fastify)
98
337
  })
338
+
339
+ fastify.decorate(
340
+ "authenticatedOrWorksession",
341
+ (pluginKey: string) => (req: FastifyRequest, res: FastifyReply) =>
342
+ authenticatedOrWorksession(req, res, fastify, pluginKey),
343
+ )
344
+ fastify.decorate(
345
+ "checkPermissions",
346
+ (permissionsArr: Array<String>) =>
347
+ (req: FastifyRequest, res: FastifyReply) =>
348
+ checkPermissions(req, res, fastify, permissionsArr),
349
+ )
99
350
  })
@@ -0,0 +1,21 @@
1
+ import type { FastifyInstance } from "fastify"
2
+ import fastifyPlugin from "fastify-plugin"
3
+ import rateLimit from "@fastify/rate-limit"
4
+ import configService from "@c/config/configService"
5
+
6
+ export default fastifyPlugin(async (fastify: FastifyInstance) => {
7
+ const opts = configService.getFastifyRateLimits()
8
+ fastify.log.info("Fastify Ratelimiter with opts " + JSON.stringify(opts))
9
+ await fastify.register(rateLimit, opts)
10
+ fastify.setNotFoundHandler(
11
+ {
12
+ preHandler: fastify.rateLimit({
13
+ max: 4,
14
+ timeWindow: 500,
15
+ }),
16
+ },
17
+ function (request, reply) {
18
+ reply.code(404).send({ why: "you do dat?" })
19
+ },
20
+ )
21
+ })
@@ -12,11 +12,22 @@ import { HttpService } from "../core/http/httpTypes"
12
12
  import { UserDoc } from "@c/user/user.schema"
13
13
  import { FrontendPayloadRequestData } from "@/utils/request.utils"
14
14
  import { FastifyRequest, FastifyReply } from "fastify"
15
+ import type RateLimit from "@fastify/rate-limit"
16
+ import type { RequestWorkSession } from "@/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema"
17
+
18
+ export type { RequestWorkSession }
15
19
 
16
20
  declare module "fastify" {
17
21
  interface FastifyInstance {
18
22
  database: Connection
23
+ rateLimit: RateLimit
19
24
  authenticate: (req: FastifyRequest, res: FastifyReply) => Promise<void>
25
+ authenticatedOrWorksession: (
26
+ pluginKey: string,
27
+ ) => (req: FastifyRequest, res: FastifyReply) => Promise<void>
28
+ checkPermissions: (
29
+ permissions: string,
30
+ ) => (req: FastifyRequest, res: FastifyReply) => Promise<void>
20
31
  wss: Socket<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any>
21
32
  eventbus: EventEmitter
22
33
  cache: ValkeyService
@@ -32,6 +43,12 @@ declare module "fastify" {
32
43
  database: Connection
33
44
  user: UserDoc
34
45
  requestParams: FrontendPayloadRequestData
46
+ /** Set by authenticatedOrWorksession when request is via work session */
47
+ workSession?: RequestWorkSession
48
+ /** Set by authenticatedOrWorksession when request is via work session */
49
+ isWorkSession?: boolean
50
+ /** Project ID from user (lastProjectId) or workSession.project */
51
+ projectId?: string
35
52
  }
36
53
 
37
54
  interface RouteOptions {
@@ -11,13 +11,16 @@ export const createRequestParams = (
11
11
  req: RequestWithCommonHeaders,
12
12
  config: FastifyContextConfig,
13
13
  ): FrontendPayloadRequestData => {
14
+ // Get project from req.projectId (set by auth middleware), header, or empty string
15
+ const projectId = (req as any).projectId ?? req.headers["current-project"] ?? ""
16
+
14
17
  const requestParams = {
15
18
  appSessionId: req.headers["app-session-id"] ?? "",
16
19
  appRequestCode: req.headers["app-request-code"] ?? "",
17
20
  requestId: req.id ?? "",
18
21
  type: "",
19
22
  broadcast: req.headers["app-broadcast-channels"] ?? false,
20
- project: req.headers["current-project"] ?? "",
23
+ project: projectId,
21
24
  userId: req.user?.id,
22
25
  options: {},
23
26
  ...config,
@@ -1 +1,15 @@
1
- export * from "../src/types"
1
+ export {
2
+ RacletteAppMode,
3
+ RacletteConfig,
4
+ WorkbenchConfig,
5
+ ServiceFrontendConfig,
6
+ ServiceBackendConfig,
7
+ EnvironmentVariables,
8
+ VolumeDefinition,
9
+ CacheConfig,
10
+ SocketConfig,
11
+ RacletteModule,
12
+ PluginMetadata,
13
+ PackageSourceConfig,
14
+ PackageTargetsTuple,
15
+ PackageTarget } from "../src/types"
@@ -268,6 +268,15 @@
268
268
  "@fastify/forwarded" "^3.0.0"
269
269
  ipaddr.js "^2.1.0"
270
270
 
271
+ "@fastify/rate-limit@10.3.0":
272
+ version "10.3.0"
273
+ resolved "https://registry.yarnpkg.com/@fastify/rate-limit/-/rate-limit-10.3.0.tgz#3cf6a56c0e3dd18fc0a56727675d7ba1d9a9bd7b"
274
+ integrity sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==
275
+ dependencies:
276
+ "@lukeed/ms" "^2.0.2"
277
+ fastify-plugin "^5.0.0"
278
+ toad-cache "^3.7.0"
279
+
271
280
  "@fastify/schedule@6.0.0":
272
281
  version "6.0.0"
273
282
  resolved "https://registry.yarnpkg.com/@fastify/schedule/-/schedule-6.0.0.tgz#0a82d2c49e9f2411b9aee312c5eb178b1dceb3fc"
@@ -760,10 +769,10 @@ avvio@^9.0.0:
760
769
  "@fastify/error" "^4.0.0"
761
770
  fastq "^1.17.1"
762
771
 
763
- axios@1.12.2:
764
- version "1.12.2"
765
- resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.2.tgz#6c307390136cf7a2278d09cec63b136dfc6e6da7"
766
- integrity sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==
772
+ axios@1.13.2:
773
+ version "1.13.2"
774
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687"
775
+ integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==
767
776
  dependencies:
768
777
  follow-redirects "^1.15.6"
769
778
  form-data "^4.0.4"
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" href="/favicon.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>raclette - Come in and have some Cheese</title>
7
+ <title>racletteJS - Come in and have some Cheese</title>
8
8
  <link rel="manifest" href="./manifest.json" accept-version="*" />
9
9
  <script>
10
10
  window.configs = false
File without changes
@@ -59,7 +59,7 @@ export class ConfigService {
59
59
  public getConfig() {
60
60
  if (!this.config) {
61
61
  if (!cachedConfig) {
62
- console.error("Failed to load Raclette configuration")
62
+ console.error("Failed to load racletteJS configuration")
63
63
  }
64
64
  this.config = cachedConfig
65
65
  }
@@ -389,7 +389,7 @@ const addItemDefaulAttributes = (item, user) => {
389
389
  * Will store the current time to the localStorage to determine the last time we had contact with the backend
390
390
  */
391
391
  const rememberLastDataUpdateRetrieval = (projectId) => {
392
- const currentProject = projectId || globalApplicationName
392
+ const currentProject = projectId
393
393
 
394
394
  log.api("rememberLastDataUpdateRetrieval", currentProject)
395
395
  localStorage.setItem(`lastUpdate_${currentProject}`, new Date().toISOString())
@@ -1,5 +1,6 @@
1
1
  export default () => {
2
- let snowflakes_count = 100
2
+ // Reduced default count for better performance
3
+ let snowflakes_count = 20
3
4
 
4
5
  if (typeof total !== "undefined") {
5
6
  snowflakes_count = total
@@ -27,11 +28,11 @@ export default () => {
27
28
 
28
29
  // Set CSS custom properties for each snowflake
29
30
  const randomX = Math.random() * 100
30
- const randomScale = 0.3 + Math.random() * 0.7 // Scale between 0.3 and 1
31
- const randomDuration = 8 + Math.random() * 15 // Duration between 8-23s
32
- const randomDelay = Math.random() * 10 // Delay between 0-10s
33
- const randomOpacity = 0.3 + Math.random() * 0.7 // Opacity between 0.3-1
34
- const randomAnimation = Math.floor(Math.random() * 3) + 1 // Choose from 3 animations
31
+ const randomScale = 0.3 + Math.random() * 0.7
32
+ const randomDuration = 10 + Math.random() * 15 // Slower = less GPU work
33
+ const randomDelay = Math.random() * 15
34
+ const randomOpacity = 0.4 + Math.random() * 0.6
35
+ const randomAnimation = Math.floor(Math.random() * 3) + 1
35
36
 
36
37
  snowflake.style.setProperty("--start-x", `${randomX}%`)
37
38
  snowflake.style.setProperty("--scale", randomScale.toString())
@@ -46,7 +47,7 @@ export default () => {
46
47
  document.getElementById("snow")?.appendChild(fragment)
47
48
  }
48
49
 
49
- // Optimized CSS with GPU acceleration and simplified animations
50
+ // Highly optimized CSS with maximum performance settings
50
51
  const createOptimizedCSS = () => {
51
52
  const css = `
52
53
  #snow {
@@ -58,46 +59,74 @@ export default () => {
58
59
  pointer-events: none;
59
60
  z-index: 1000;
60
61
  overflow: hidden;
62
+ /* Contain layout calculations */
63
+ contain: layout style paint;
61
64
  }
62
65
 
63
66
  .snowflake {
64
67
  position: absolute;
65
68
  top: -10px;
66
- width: 8px;
67
- height: 8px;
68
- background: white;
69
+ width: 6px;
70
+ height: 6px;
71
+ background: rgba(255, 255, 255, 0.9);
69
72
  border-radius: 50%;
70
73
 
71
- /* GPU acceleration - critical for performance */
74
+ /* Critical GPU optimizations */
72
75
  will-change: transform;
73
76
  transform: translateZ(0);
74
77
  backface-visibility: hidden;
75
78
 
76
- /* Simplified glow effect - much lighter than drop-shadow filter */
77
- box-shadow: 0 0 6px rgba(255, 255, 255, 0.6);
79
+ /* REMOVED box-shadow - this is a major performance killer */
80
+ /* Even lightweight shadows are expensive with many elements */
78
81
 
79
82
  /* Use CSS custom properties for individual variations */
80
83
  left: var(--start-x);
81
84
  opacity: var(--opacity);
82
85
  animation: var(--animation-name) var(--duration) var(--delay) linear infinite;
86
+
87
+ /* Reduce repaints by using contain */
88
+ contain: layout style paint;
83
89
  }
84
90
 
85
- /* Simplified to 3 falling patterns - uses only transform for best performance */
91
+ /* Ultra-optimized animations - only transform property */
92
+ /* Grouped transforms reduce compositing work */
86
93
  @keyframes fall-1 {
87
- to {
88
- transform: translateY(100vh) translateX(20px) scale(var(--scale)) translateZ(0);
94
+ 0% {
95
+ transform: translate3d(0, 0, 0) scale(var(--scale));
96
+ }
97
+ 100% {
98
+ transform: translate3d(20px, 100vh, 0) scale(var(--scale));
89
99
  }
90
100
  }
91
101
 
92
102
  @keyframes fall-2 {
93
- to {
94
- transform: translateY(100vh) translateX(-20px) scale(var(--scale)) translateZ(0);
103
+ 0% {
104
+ transform: translate3d(0, 0, 0) scale(var(--scale));
105
+ }
106
+ 100% {
107
+ transform: translate3d(-20px, 100vh, 0) scale(var(--scale));
95
108
  }
96
109
  }
97
110
 
98
111
  @keyframes fall-3 {
99
- to {
100
- transform: translateY(100vh) translateX(0px) scale(var(--scale)) translateZ(0);
112
+ 0% {
113
+ transform: translate3d(0, 0, 0) scale(var(--scale));
114
+ }
115
+ 100% {
116
+ transform: translate3d(0, 100vh, 0) scale(var(--scale));
117
+ }
118
+ }
119
+
120
+ /* Pause animations when snow container is hidden to save resources */
121
+ #snow[style*="display: none"] .snowflake {
122
+ animation-play-state: paused;
123
+ }
124
+
125
+ /* Reduce motion for users who prefer it */
126
+ @media (prefers-reduced-motion: reduce) {
127
+ .snowflake {
128
+ animation: none;
129
+ display: none;
101
130
  }
102
131
  }
103
132
  `
@@ -0,0 +1 @@
1
+ export * from "./removeEmpty"
@@ -0,0 +1,69 @@
1
+ type EmptyCheckConfig = {
2
+ removeNull?: boolean
3
+ removeUndefined?: boolean
4
+ removeFalse?: boolean
5
+ removeEmptyString?: boolean
6
+ removeZero?: boolean
7
+ removeEmptyArray?: boolean
8
+ removeEmptyObject?: boolean
9
+ customCheck?: (value: unknown) => boolean
10
+ }
11
+
12
+ const DEFAULT_CONFIG: Required<Omit<EmptyCheckConfig, "customCheck">> = {
13
+ removeNull: true,
14
+ removeUndefined: true,
15
+ removeFalse: false,
16
+ removeEmptyString: false,
17
+ removeZero: false,
18
+ removeEmptyArray: false,
19
+ removeEmptyObject: false,
20
+ }
21
+
22
+ const isEmpty = (value: unknown, config: EmptyCheckConfig): boolean => {
23
+ const checks: Array<() => boolean> = [
24
+ () => config.removeNull === true && value === null,
25
+ () => config.removeUndefined === true && value === undefined,
26
+ () => config.removeFalse === true && value === false,
27
+ () => config.removeEmptyString === true && value === "",
28
+ () => config.removeZero === true && value === 0,
29
+ () =>
30
+ config.removeEmptyArray === true &&
31
+ Array.isArray(value) &&
32
+ value.length === 0,
33
+ () =>
34
+ config.removeEmptyObject === true &&
35
+ typeof value === "object" &&
36
+ value !== null &&
37
+ !Array.isArray(value) &&
38
+ Object.keys(value).length === 0,
39
+ () => config.customCheck?.(value) ?? false,
40
+ ]
41
+
42
+ return checks.some((check) => check())
43
+ }
44
+
45
+ const recurse = (value: unknown, config: EmptyCheckConfig): unknown => {
46
+ if (Array.isArray(value)) {
47
+ return value
48
+ .map((item) => recurse(item, config))
49
+ .filter((item) => !isEmpty(item, config))
50
+ }
51
+
52
+ if (typeof value === "object" && value !== null) {
53
+ return Object.fromEntries(
54
+ Object.entries(value)
55
+ .map(([key, val]) => [key, recurse(val, config)] as const)
56
+ .filter(([, val]) => !isEmpty(val, config)),
57
+ )
58
+ }
59
+
60
+ return value
61
+ }
62
+
63
+ export const removeEmptyValues = <T extends object>(
64
+ obj: T,
65
+ config: EmptyCheckConfig = {},
66
+ ): Partial<T> => {
67
+ const mergedConfig = { ...DEFAULT_CONFIG, ...config }
68
+ return recurse(obj, mergedConfig) as Partial<T>
69
+ }