@raclettejs/core 0.1.17 → 0.1.19

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/CHANGELOG.md +30 -1
  2. package/package.json +2 -2
  3. package/services/backend/package.json +2 -2
  4. package/services/backend/src/core/config/configService.ts +4 -0
  5. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +86 -0
  6. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/index.ts +4 -0
  7. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.bulk.ts +58 -0
  8. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.restore.ts +55 -0
  9. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.service.ts +96 -9
  10. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.ts +4 -0
  11. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.bulk.ts +60 -0
  12. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.restore.ts +55 -0
  13. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.model.ts +17 -0
  14. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.schema.ts +18 -6
  15. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/index.ts +5 -5
  16. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/index.ts +4 -0
  17. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.bulk.ts +58 -0
  18. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.restore.ts +55 -0
  19. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +74 -1
  20. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/index.ts +2 -0
  21. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.restore.ts +55 -0
  22. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.service.ts +1 -0
  23. package/services/backend/src/domains/system/routes/route.user.login.ts +4 -2
  24. package/services/backend/src/shared/types/core/ProjectUpdate.types.ts +1 -0
  25. package/services/backend/src/shared/types/dataTypes/tags.ts +1 -0
  26. package/services/frontend/package.json +8 -8
  27. package/services/frontend/src/core/lib/configService.ts +1 -1
  28. package/services/frontend/src/core/lib/data/dataApi.ts +10 -1
  29. package/services/frontend/src/core/lib/data/fetchDataHandler.ts +3 -23
  30. package/services/frontend/src/core/lib/data/writeDataHandler.ts +8 -13
  31. package/services/frontend/src/core/lib/dataHelper.ts +15 -0
  32. package/services/frontend/src/core/lib/eggs/snow.ts +32 -43
  33. package/services/frontend/src/core/lib/eventWhitelist.ts +4 -13
  34. package/services/frontend/src/core/main.ts +7 -1
  35. package/services/frontend/src/orchestrator/LoginApp.vue +0 -1
  36. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +40 -5
  37. package/services/frontend/src/orchestrator/components/composition/CompositionOverlay.vue +7 -3
  38. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +12 -24
  39. package/services/frontend/src/orchestrator/components/input/SimpleSearchInput.vue +16 -4
  40. package/services/frontend/src/orchestrator/components/menu/AppBarMenu.vue +184 -0
  41. package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +62 -46
  42. package/services/frontend/src/orchestrator/components/navigation/PageNavigationSidebar.vue +11 -1
  43. package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +10 -1
  44. package/services/frontend/src/orchestrator/i18n/de-DE.json +15 -0
  45. package/services/frontend/src/orchestrator/i18n/en-EU.json +15 -0
  46. package/services/frontend/src/orchestrator/i18n/sk.json +15 -0
  47. package/services/frontend/src/orchestrator/router/index.ts +16 -0
  48. package/yarn.lock +4 -4
  49. package/services/frontend/src/orchestrator/setup/loginApp.ts +0 -27
package/CHANGELOG.md CHANGED
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.19] - 12.12.2025 <a href="https://gitlab.com/raclettejs/core/-/compare/v0.1.18...v0.1.19" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
11
+
12
+ ### Added
13
+
14
+ - Global: added bulk import to interactionLinks, compositions and tags
15
+
16
+ ## [0.1.18] - 05.12.2025 <a href="https://gitlab.com/raclettejs/core/-/compare/v0.1.17...v0.1.18" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
17
+
18
+ ### Changed
19
+
20
+ - Backend: project data type deletion routes temporarily disabled. Will be reenabled soon
21
+ - Frontend: updated dependencies
22
+
23
+ ### Added
24
+
25
+ - Frontend: usermenu - added alternate render mode for inline composition if no trigger id/title is set
26
+ - Frontend: Appbar can now house interactionLinks and inline compositions
27
+ - Frontend: appbar can now launch compositions in a floating window
28
+ - Backend: added restore endpoints for tag/user/composition/interactionLink
29
+ - Frontend: Token expiry will now redirect automatically to login. Last location during auth loss will be used for redirect later
30
+ - Frontend: Simple search input can now receive custom label and uses debounce before model update
31
+ - Frontend: PageNavigation can now rail
32
+ - Backend: Project dataType can now hold config values
33
+ - Global: Added new Tagtype "event"
34
+
35
+ ### Fixed
36
+
37
+ - Backend: isDeleted handling in getAll routes. isDeleted needs to be explicitly true to see deleted entries
38
+ - Backend: TTL for auth token is not derived from raclette.config
39
+
10
40
  ## [0.1.17] - 06.11.2025 <a href="https://gitlab.com/raclettejs/core/-/compare/v0.1.15...v0.1.17" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
11
41
 
12
42
  ### Fixed
@@ -14,7 +44,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
44
  - Frontend: axios Delete handling
15
45
  - Frontend: retrieval of deleted compositions/interactionLinks during socket join
16
46
  - Frontend: notifications for delete events now show name
17
- -
18
47
 
19
48
  ### Added
20
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raclettejs/core",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "racletteJS core package",
5
5
  "repository": "https://gitlab.com/raclettejs/core",
6
6
  "author": "Pacifico Digital Explorations GmbH <info@raclettejs.com>",
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@eslint/js": "9.35.0",
72
- "@raclettejs/types": "^0.1.17",
72
+ "@raclettejs/types": "0.1.19",
73
73
  "@types/fs-extra": "11.0.4",
74
74
  "@types/js-yaml": "4.0.9",
75
75
  "@types/node": "24.5.1",
@@ -30,7 +30,7 @@
30
30
  "@fastify/sensible": "6.0.3",
31
31
  "@sinclair/typebox": "0.34.41",
32
32
  "@types/vary": "1.1.3",
33
- "axios": "1.12.2",
33
+ "axios": "1.13.2",
34
34
  "bcrypt": "6.0.0",
35
35
  "copyfiles": "2.4.1",
36
36
  "fastify": "5.6.0",
@@ -62,4 +62,4 @@
62
62
  "typescript": "5.9.2",
63
63
  "typescript-eslint": "8.44.0"
64
64
  }
65
- }
65
+ }
@@ -13,6 +13,7 @@ type RacletteBackendConfig = {
13
13
  plugins?: string[]
14
14
  global?: {
15
15
  requireAuthentication?: boolean
16
+ authTokenExpiryDays?: number
16
17
  }
17
18
  additionalPluginPaths?: string[]
18
19
  }
@@ -130,6 +131,9 @@ export class ConfigService {
130
131
  public isAuthRequired(): boolean {
131
132
  return this.config?.global?.requireAuthentication ?? true
132
133
  }
134
+ public getAuthTokenExpiryDays(): number {
135
+ return this.config?.global?.authTokenExpiryDays ?? 30
136
+ }
133
137
  public isAdminTagRequired(): boolean {
134
138
  return this.config?.onlyAllowLoginWithAdminTag ?? false
135
139
  }
@@ -97,6 +97,8 @@ export class CompositionService {
97
97
  filters: Record<string, any> = {},
98
98
  ): Promise<CompositionType[]> {
99
99
  try {
100
+ if (filters.isDeleted !== true && filters.isDeleted !== "true")
101
+ filters.isDeleted = false
100
102
  return await this.compositionModel
101
103
  .find({
102
104
  ...filters,
@@ -335,6 +337,90 @@ export class CompositionService {
335
337
  payload.body.deleted = [composition._id]
336
338
  fastify.emit("compositionHardDeleted", payload)
337
339
 
340
+ return payload
341
+ }
342
+ /**
343
+ * Core function to create multiple compositions (returns raw data)
344
+ */
345
+ async _createCompositions(
346
+ fastify: PluginFastifyInstance,
347
+ compositionBodies: CompositionBody[],
348
+ userId: string,
349
+ ): Promise<CompositionType[]> {
350
+ try {
351
+ const compositionsToCreate: CompositionBody[] = []
352
+
353
+ // Validate and prepare all compositions
354
+ for (const compositionBody of compositionBodies) {
355
+ if (compositionBody._id) {
356
+ const uuidValid = validate(compositionBody._id)
357
+
358
+ if (!uuidValid) {
359
+ throw new Error(
360
+ `Invalid ID - not a valid uuid v4: ${compositionBody._id}`,
361
+ )
362
+ }
363
+
364
+ const duplicate = await this.compositionModel.findOne({
365
+ _id: compositionBody._id,
366
+ })
367
+
368
+ if (duplicate) {
369
+ throw new Error(
370
+ `An entry with this id already exists: ${compositionBody._id}`,
371
+ )
372
+ }
373
+ } else {
374
+ compositionBody._id = uuidv4()
375
+ }
376
+
377
+ compositionsToCreate.push({
378
+ ...compositionBody,
379
+ author: userId,
380
+ lastEditor: userId,
381
+ })
382
+ }
383
+
384
+ // Bulk insert
385
+ const compositions =
386
+ await this.compositionModel.insertMany(compositionsToCreate)
387
+
388
+ fastify.log.info(
389
+ `[API] Created ${compositions.length} compositions in bulk`,
390
+ )
391
+
392
+ return compositions.map((comp) =>
393
+ comp.toObject ? comp.toObject() : comp,
394
+ )
395
+ } catch (error: any) {
396
+ fastify.log.error(error.message)
397
+ throw error
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Create multiple compositions with payload wrapping and event emission
403
+ */
404
+ async createCompositions(
405
+ fastify: PluginFastifyInstance,
406
+ requestData: FrontendPayloadRequestData,
407
+ compositionBodies: CompositionBody[],
408
+ userId: string,
409
+ ): Promise<FrontendPayload<CompositionType[]>> {
410
+ const compositions = await this._createCompositions(
411
+ fastify,
412
+ compositionBodies,
413
+ userId,
414
+ )
415
+
416
+ const payload = await createCompositionPayload(
417
+ fastify,
418
+ compositions,
419
+ requestData,
420
+ )
421
+
422
+ fastify.emit("compositionsCreated", payload)
423
+
338
424
  return payload
339
425
  }
340
426
  }
@@ -4,13 +4,17 @@ import getAll from "./route.composition.getAll"
4
4
  import hardDelete from "./route.composition.hardDelete"
5
5
  import patch from "./route.composition.patch"
6
6
  import post from "./route.composition.post"
7
+ import postBulk from "./route.composition.post.bulk"
7
8
  import remove from "./route.composition.remove"
9
+ import restore from "./route.composition.restore"
8
10
 
9
11
  const registerRoutes = async (fastify: PluginFastifyInstance) => {
10
12
  await fastify.get("/composition/all", getAll(fastify))
11
13
  await fastify.get("/composition/:_id", get(fastify))
12
14
  await fastify.patch("/composition/:_id", patch(fastify))
15
+ await fastify.patch("/composition/:_id/restore", restore(fastify))
13
16
  await fastify.post("/composition", post(fastify))
17
+ await fastify.post("/composition/bulk", postBulk(fastify))
14
18
  await fastify.delete("/composition/:_id", remove(fastify))
15
19
  await fastify.delete("/composition/:_id/hard", hardDelete(fastify))
16
20
  }
@@ -0,0 +1,58 @@
1
+ import type { CompositionBody } from "../composition.service"
2
+ import type { FastifyReply, FastifyRequest } from "fastify"
3
+ import type { PluginFastifyInstance } from "types"
4
+ import { compositionCreateSchema } from "../composition.schema"
5
+
6
+ export default (fastify: PluginFastifyInstance) => {
7
+ const compositionService = fastify.custom.compositionService
8
+
9
+ const handler = async (
10
+ req: FastifyRequest<{
11
+ Body: CompositionBody[]
12
+ }>,
13
+ reply: FastifyReply,
14
+ ) => {
15
+ try {
16
+ // Validate that body is an array
17
+ if (!Array.isArray(req.body)) {
18
+ return reply.badRequest("Request body must be an array of compositions")
19
+ }
20
+
21
+ // Validate that array is not empty
22
+ if (req.body.length === 0) {
23
+ return reply.badRequest("Request body cannot be an empty array")
24
+ }
25
+
26
+ const payload = await compositionService.createCompositions(
27
+ fastify,
28
+ req.requestParams,
29
+ req.body,
30
+ req.user._id,
31
+ )
32
+
33
+ return payload
34
+ } catch (error: any) {
35
+ fastify.log.error(error.message)
36
+
37
+ return reply.internalServerError(error.message)
38
+ }
39
+ }
40
+
41
+ return {
42
+ handler,
43
+ onRequest: [fastify.authenticate],
44
+ config: {
45
+ type: "dataCreate",
46
+ broadcastChannels: ["compositionsCreated"],
47
+ },
48
+ schema: {
49
+ summary: "Create multiple compositions in bulk",
50
+ tags: ["core/composition"],
51
+ body: {
52
+ type: "array",
53
+ items: compositionCreateSchema,
54
+ minItems: 1,
55
+ },
56
+ },
57
+ }
58
+ }
@@ -0,0 +1,55 @@
1
+ import type { Static } from "@sinclair/typebox"
2
+ import type { FastifyReply, FastifyRequest } from "fastify"
3
+ import { PluginFastifyInstance } from "types"
4
+ import { Type } from "@sinclair/typebox"
5
+ import { ForbiddenError } from "@/utils/errors"
6
+
7
+ const ParamsSchema = Type.Object({
8
+ _id: Type.String(),
9
+ })
10
+ type Params = Static<typeof ParamsSchema>
11
+
12
+ export default (fastify: PluginFastifyInstance) => {
13
+ const compositionService = fastify.custom.compositionService
14
+
15
+ const handler = async (
16
+ req: FastifyRequest<{ Params: Params }>,
17
+ reply: FastifyReply,
18
+ ) => {
19
+ try {
20
+ if (req.user.isAdmin) {
21
+ const { _id } = req.params
22
+
23
+ const payload = await compositionService.updateComposition(
24
+ fastify,
25
+ req.requestParams,
26
+ _id,
27
+ { isDeleted: false },
28
+ req.user._id,
29
+ )
30
+
31
+ return payload
32
+ } else {
33
+ throw new ForbiddenError("Only admins can restore this core datatype")
34
+ }
35
+ } catch (error: any) {
36
+ fastify.log.error(error.message)
37
+ return reply.internalServerError(error.message)
38
+ }
39
+ }
40
+
41
+ return {
42
+ handler,
43
+ onRequest: [fastify.authenticate],
44
+ config: {
45
+ type: "dataRestore",
46
+ broadcastChannels: ["compositionRestored"],
47
+ },
48
+ schema: {
49
+ summary: "restore a composition by id",
50
+ description: "Restore a composition",
51
+ tags: ["core/composition"],
52
+ params: ParamsSchema,
53
+ },
54
+ }
55
+ }
@@ -100,6 +100,8 @@ export class InteractionLinkService {
100
100
  filters: Record<string, any> = {},
101
101
  ): Promise<InteractionLinkType[]> {
102
102
  try {
103
+ if (filters.isDeleted !== true && filters.isDeleted !== "true")
104
+ filters.isDeleted = false
103
105
  return await this.interactionLinkModel
104
106
  .find({
105
107
  ...filters,
@@ -128,16 +130,16 @@ export class InteractionLinkService {
128
130
  }
129
131
 
130
132
  /**
131
- * Core function to get interaction links by composition ID (returns raw data)
133
+ * Core function to get interaction links by interactionLink ID (returns raw data)
132
134
  */
133
- async _readInteractionLinksByComposition(
135
+ async _readInteractionLinksByInteractionLink(
134
136
  fastify: PluginFastifyInstance,
135
- compositionId: string,
137
+ interactionLinkId: string,
136
138
  ): Promise<InteractionLinkType[]> {
137
139
  try {
138
140
  return await this.interactionLinkModel
139
141
  .find({
140
- composition: compositionId,
142
+ interactionLink: interactionLinkId,
141
143
  isDeleted: false,
142
144
  })
143
145
  .lean()
@@ -148,16 +150,16 @@ export class InteractionLinkService {
148
150
  }
149
151
 
150
152
  /**
151
- * Get interaction links by composition ID with payload wrapping
153
+ * Get interaction links by interactionLink ID with payload wrapping
152
154
  */
153
- async readInteractionLinksByComposition(
155
+ async readInteractionLinksByInteractionLink(
154
156
  fastify: PluginFastifyInstance,
155
157
  requestData: FrontendPayloadRequestData,
156
- compositionId: string,
158
+ interactionLinkId: string,
157
159
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
158
- const interactionLinks = await this._readInteractionLinksByComposition(
160
+ const interactionLinks = await this._readInteractionLinksByInteractionLink(
159
161
  fastify,
160
- compositionId,
162
+ interactionLinkId,
161
163
  )
162
164
 
163
165
  return createInteractionLinkPayload(fastify, interactionLinks, requestData)
@@ -437,6 +439,91 @@ export class InteractionLinkService {
437
439
 
438
440
  return createInteractionLinkPayload(fastify, interactionLinks, requestData)
439
441
  }
442
+ /**
443
+ * Core function to create multiple interactionLinks (returns raw data)
444
+ */
445
+ async _createInteractionLinks(
446
+ fastify: PluginFastifyInstance,
447
+ interactionLinkBodies: InteractionLinkBody[],
448
+ userId: string,
449
+ ): Promise<InteractionLinkType[]> {
450
+ try {
451
+ const interactionLinksToCreate: InteractionLinkBody[] = []
452
+
453
+ // Validate and prepare all interactionLinks
454
+ for (const interactionLinkBody of interactionLinkBodies) {
455
+ if (interactionLinkBody._id) {
456
+ const uuidValid = validate(interactionLinkBody._id)
457
+
458
+ if (!uuidValid) {
459
+ throw new Error(
460
+ `Invalid ID - not a valid uuid v4: ${interactionLinkBody._id}`,
461
+ )
462
+ }
463
+
464
+ const duplicate = await this.interactionLinkModel.findOne({
465
+ _id: interactionLinkBody._id,
466
+ })
467
+
468
+ if (duplicate) {
469
+ throw new Error(
470
+ `An entry with this id already exists: ${interactionLinkBody._id}`,
471
+ )
472
+ }
473
+ } else {
474
+ interactionLinkBody._id = uuidv4()
475
+ }
476
+
477
+ interactionLinksToCreate.push({
478
+ ...interactionLinkBody,
479
+ author: userId,
480
+ lastEditor: userId,
481
+ })
482
+ }
483
+
484
+ // Bulk insert
485
+ const interactionLinks = await this.interactionLinkModel.insertMany(
486
+ interactionLinksToCreate,
487
+ )
488
+
489
+ fastify.log.info(
490
+ `[API] Created ${interactionLinks.length} interactionLinks in bulk`,
491
+ )
492
+
493
+ return interactionLinks.map((comp) =>
494
+ comp.toObject ? comp.toObject() : comp,
495
+ )
496
+ } catch (error: any) {
497
+ fastify.log.error(error.message)
498
+ throw error
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Create multiple interactionLinks with payload wrapping and event emission
504
+ */
505
+ async createInteractionLinks(
506
+ fastify: PluginFastifyInstance,
507
+ requestData: FrontendPayloadRequestData,
508
+ interactionLinkBodies: InteractionLinkBody[],
509
+ userId: string,
510
+ ): Promise<FrontendPayload<InteractionLinkType[]>> {
511
+ const interactionLinks = await this._createInteractionLinks(
512
+ fastify,
513
+ interactionLinkBodies,
514
+ userId,
515
+ )
516
+
517
+ const payload = await createInteractionLinkPayload(
518
+ fastify,
519
+ interactionLinks,
520
+ requestData,
521
+ )
522
+
523
+ fastify.emit("interactionLinksCreated", payload)
524
+
525
+ return payload
526
+ }
440
527
  }
441
528
 
442
529
  export const createInteractionLinkService = (
@@ -4,13 +4,17 @@ import getAll from "./route.interactionLink.getAll"
4
4
  import hardDelete from "./route.interactionLink.hardDelete"
5
5
  import patch from "./route.interactionLink.patch"
6
6
  import post from "./route.interactionLink.post"
7
+ import postBulk from "./route.interactionLink.post.bulk"
7
8
  import remove from "./route.interactionLink.remove"
9
+ import restore from "./route.interactionLink.restore"
8
10
 
9
11
  const registerRoutes = async (fastify: PluginFastifyInstance) => {
10
12
  await fastify.get("/interactionLink/all", getAll(fastify))
11
13
  await fastify.get("/interactionLink/:_id", get(fastify))
12
14
  await fastify.patch("/interactionLink/:_id", patch(fastify))
15
+ await fastify.patch("/interactionLink/:_id/restore", restore(fastify))
13
16
  await fastify.post("/interactionLink", post(fastify))
17
+ await fastify.post("/interactionLink/bulk", postBulk(fastify))
14
18
  await fastify.delete("/interactionLink/:_id", remove(fastify))
15
19
  await fastify.delete("/interactionLink/:_id/hard", hardDelete(fastify))
16
20
  }
@@ -0,0 +1,60 @@
1
+ import type { InteractionLinkBody } from "../interactionLink.service"
2
+ import type { FastifyReply, FastifyRequest } from "fastify"
3
+ import type { PluginFastifyInstance } from "types"
4
+ import { interactionLinkCreateSchema } from "../interactionLink.schema"
5
+
6
+ export default (fastify: PluginFastifyInstance) => {
7
+ const interactionLinkService = fastify.custom.interactionLinkService
8
+
9
+ const handler = async (
10
+ req: FastifyRequest<{
11
+ Body: InteractionLinkBody[]
12
+ }>,
13
+ reply: FastifyReply,
14
+ ) => {
15
+ try {
16
+ // Validate that body is an array
17
+ if (!Array.isArray(req.body)) {
18
+ return reply.badRequest(
19
+ "Request body must be an array of interactionLinks",
20
+ )
21
+ }
22
+
23
+ // Validate that array is not empty
24
+ if (req.body.length === 0) {
25
+ return reply.badRequest("Request body cannot be an empty array")
26
+ }
27
+
28
+ const payload = await interactionLinkService.createInteractionLinks(
29
+ fastify,
30
+ req.requestParams,
31
+ req.body,
32
+ req.user._id,
33
+ )
34
+
35
+ return payload
36
+ } catch (error: any) {
37
+ fastify.log.error(error.message)
38
+
39
+ return reply.internalServerError(error.message)
40
+ }
41
+ }
42
+
43
+ return {
44
+ handler,
45
+ onRequest: [fastify.authenticate],
46
+ config: {
47
+ type: "dataCreate",
48
+ broadcastChannels: ["interactionLinksCreated"],
49
+ },
50
+ schema: {
51
+ summary: "Create multiple interactionLinks in bulk",
52
+ tags: ["core/interactionLink"],
53
+ body: {
54
+ type: "array",
55
+ items: interactionLinkCreateSchema,
56
+ minItems: 1,
57
+ },
58
+ },
59
+ }
60
+ }
@@ -0,0 +1,55 @@
1
+ import type { Static } from "@sinclair/typebox"
2
+ import type { FastifyReply, FastifyRequest } from "fastify"
3
+ import { PluginFastifyInstance } from "types"
4
+ import { Type } from "@sinclair/typebox"
5
+ import { ForbiddenError } from "@/utils/errors"
6
+
7
+ const ParamsSchema = Type.Object({
8
+ _id: Type.String(),
9
+ })
10
+ type Params = Static<typeof ParamsSchema>
11
+
12
+ export default (fastify: PluginFastifyInstance) => {
13
+ const interactionLinkService = fastify.custom.interactionLinkService
14
+
15
+ const handler = async (
16
+ req: FastifyRequest<{ Params: Params }>,
17
+ reply: FastifyReply,
18
+ ) => {
19
+ try {
20
+ if (req.user.isAdmin) {
21
+ const { _id } = req.params
22
+
23
+ const payload = await interactionLinkService.updateInteractionLink(
24
+ fastify,
25
+ req.requestParams,
26
+ _id,
27
+ { isDeleted: false },
28
+ req.user._id,
29
+ )
30
+
31
+ return payload
32
+ } else {
33
+ throw new ForbiddenError("Only admins can restore this core datatype")
34
+ }
35
+ } catch (error: any) {
36
+ fastify.log.error(error.message)
37
+ return reply.internalServerError(error.message)
38
+ }
39
+ }
40
+
41
+ return {
42
+ handler,
43
+ onRequest: [fastify.authenticate],
44
+ config: {
45
+ type: "dataRestore",
46
+ broadcastChannels: ["interactionLinkRestored"],
47
+ },
48
+ schema: {
49
+ summary: "restore a interactionLink by id",
50
+ description: "Restore a interactionLink",
51
+ tags: ["core/interactionLink"],
52
+ params: ParamsSchema,
53
+ },
54
+ }
55
+ }
@@ -34,6 +34,23 @@ const projectSchema = new Schema(
34
34
 
35
35
  // soft delete
36
36
  isDeleted: { type: Boolean, default: false },
37
+ config: {
38
+ type: {
39
+ tags: {
40
+ type: [Schema.Types.String],
41
+ ref: "raclette__core-tag",
42
+ default: [],
43
+ },
44
+ railPageNavigation: {
45
+ type: Boolean,
46
+ default: false,
47
+ },
48
+ },
49
+ default: () => ({
50
+ tags: [],
51
+ railPageNavigation: false,
52
+ }),
53
+ },
37
54
  },
38
55
  { timestamps: true },
39
56
  )
@@ -2,16 +2,28 @@ import type { Static } from "@sinclair/typebox"
2
2
  import type { Document } from "mongoose"
3
3
  import { Type } from "@sinclair/typebox"
4
4
  import { PluginFastifyInstance } from "types"
5
-
6
5
  /**
7
6
  * Base Project Schema - Fields common to all operations
8
7
  */
8
+ const projectConfigSchema = Type.Optional(
9
+ Type.Object(
10
+ {
11
+ tags: Type.Array(Type.String(), { default: [] }),
12
+ railPageNavigation: Type.Boolean({ default: false }),
13
+ },
14
+ {
15
+ $id: "#project/config",
16
+ title: "core/project-config",
17
+ },
18
+ ),
19
+ )
9
20
  const baseProjectSchema = {
10
21
  title: Type.Optional(Type.String({ maxLength: 200 })),
11
22
  account: Type.Optional(Type.String({ default: null })),
12
23
  tags: Type.Optional(Type.Array(Type.String(), { default: [] })),
13
24
  description: Type.Optional(Type.String()),
14
25
  isDefault: Type.Optional(Type.Boolean({ default: false })),
26
+ config: projectConfigSchema,
15
27
  }
16
28
 
17
29
  /**
@@ -51,11 +63,7 @@ export const projectCreateSchema = Type.Object(
51
63
  export const projectUpdateSchema = Type.Object(
52
64
  {
53
65
  _id: Type.Optional(Type.String()),
54
- title: Type.Optional(Type.String({ maxLength: 200 })),
55
- account: Type.Optional(Type.String()),
56
- tags: Type.Optional(Type.Array(Type.String())),
57
- description: Type.Optional(Type.String()),
58
- isDefault: Type.Optional(Type.Boolean()),
66
+ ...baseProjectSchema,
59
67
  },
60
68
  {
61
69
  $id: "#project/update",
@@ -104,4 +112,8 @@ export const registerSchemas = (fastify: PluginFastifyInstance) => {
104
112
  schema: projectUpdateSchema,
105
113
  name: "ProjectUpdate",
106
114
  })
115
+ fastify.registerSchema({
116
+ schema: projectConfigSchema,
117
+ name: "ProjectConfig",
118
+ })
107
119
  }