@raclettejs/core 0.1.18 → 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.
- package/CHANGELOG.md +23 -0
- package/bin/cli.js +1 -1
- package/dev/README.md +4 -0
- package/dist/cli.js +135 -124
- package/dist/cli.js.map +4 -4
- package/dist/index.js +9 -8
- package/dist/index.js.map +4 -4
- package/package.json +9 -5
- package/services/backend/delay-api-debugger.sh +0 -0
- package/services/backend/package.json +1 -0
- package/services/backend/src/app.ts +4 -0
- package/services/backend/src/core/config/configService.ts +10 -1
- package/services/backend/src/core/events/eventEmitter.ts +32 -7
- package/services/backend/src/core/http/request-params.plugin.ts +2 -2
- package/services/backend/src/core/pluginSystem/pluginFastifyInstance.ts +4 -1
- package/services/backend/src/core/pluginSystem/pluginTypes.ts +3 -1
- package/services/backend/src/core/sockets/createSystemEventHandlers.ts +7 -0
- package/services/backend/src/core/sockets/index.ts +61 -13
- package/services/backend/src/core/sockets/socketManager.ts +452 -105
- package/services/backend/src/core/sockets/socketTypes.ts +13 -1
- package/services/backend/src/core/sockets/workSessionJoinPayload.ts +106 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.getAll.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.hardDelete.ts +16 -19
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +86 -3
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.getAll.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.hardDelete.ts +16 -19
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.bulk.ts +60 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.service.ts +96 -12
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.getAll.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.hardDelete.ts +12 -17
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.bulk.ts +62 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.service.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.getAll.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.hardDelete.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.post.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.get.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.getAll.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.hardDelete.ts +10 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.bulk.ts +60 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +73 -3
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.get.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.getAll.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.hardDelete.ts +10 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patch.ts +4 -6
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchLastProject.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.remove.ts +3 -4
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/events/index.ts +45 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/crud.ts +12 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/payload.ts +21 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/index.ts +55 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.ts +19 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.get.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.getAll.ts +39 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.hardDelete.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.patch.ts +58 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.post.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.remove.ts +52 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.model.ts +65 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema.ts +125 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.service.ts +351 -0
- package/services/backend/src/corePlugins/raclette__core/backend/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +159 -24
- package/services/backend/src/modules/authentication/index.ts +255 -4
- package/services/backend/src/modules/hooks/rate-limiter.ts +21 -0
- package/services/backend/src/types/custom-fastify.d.ts +17 -0
- package/services/backend/src/utils/request.utils.ts +4 -1
- package/services/backend/types/index.d.ts +15 -1
- package/services/backend/yarn.lock +13 -4
- package/services/frontend/index.html +1 -1
- package/services/frontend/provision/entrypoint.sh +0 -0
- package/services/frontend/src/core/lib/configService.ts +1 -1
- package/services/frontend/src/core/lib/data/dataApi.ts +10 -1
- package/services/frontend/src/core/lib/dataHelper.ts +1 -1
- package/services/frontend/src/core/lib/eggs/snow.ts +66 -48
- package/services/frontend/src/core/lib/helpers/index.ts +1 -0
- package/services/frontend/src/core/lib/helpers/removeEmpty.ts +69 -0
- package/services/frontend/src/core/lib/httpClient.ts +19 -0
- package/services/frontend/src/core/main.ts +20 -10
- package/services/frontend/src/core/setup/socket.ts +4 -3
- package/services/frontend/src/core/setup/socketClient.ts +31 -3
- package/services/frontend/src/core/setup/socketEvents.ts +92 -8
- package/services/frontend/src/core/store/index.ts +5 -1
- package/services/frontend/src/core/store/reducers/compositions/index.ts +2 -2
- package/services/frontend/src/core/store/reducers/interactionLinks/index.ts +1 -1
- package/services/frontend/src/core/store/reducers/widgets/reducers.ts +1 -2
- package/services/frontend/src/core/store/types/index.ts +0 -1
- package/services/frontend/src/core/types/PluginRegistrar.ts +8 -6
- package/services/frontend/src/core/types/Store.ts +1 -0
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +68 -8
- package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +17 -5
- package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/usePluginApi.ts +0 -3
- package/services/frontend/src/orchestrator/composables/useRouteState.ts +23 -4
- package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +166 -0
- package/services/frontend/src/orchestrator/helpers/index.ts +1 -0
- package/services/frontend/src/orchestrator/i18n/de-DE.json +15 -1
- package/services/frontend/src/orchestrator/i18n/en-EU.json +15 -1
- package/services/frontend/src/orchestrator/i18n/sk.json +15 -1
- package/services/frontend/src/orchestrator/index.ts +34 -4
- package/services/frontend/src/orchestrator/router/routeParserHelper.ts +106 -1
- package/services/frontend/src/orchestrator/types/Widgets.ts +2 -0
- package/services/frontend/types.ts +1 -0
- package/src/README.md +404 -0
- package/src/types.ts +19 -5
- package/templates/README.md +0 -0
- package/types/index.ts +18 -4
- package/services/backend/.gitignore +0 -17
- package/services/frontend/.gitignore +0 -35
- package/yarn.lock +0 -2937
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds the joinConfirmation payload shape for work-session guests.
|
|
5
|
+
* Keeps virtual composition and simulated interaction-link logic out of socketManager.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface WorkSessionLike {
|
|
9
|
+
_id: string
|
|
10
|
+
userId: string
|
|
11
|
+
createdAt?: Date | string
|
|
12
|
+
updatedAt?: Date | string
|
|
13
|
+
[key: string]: unknown
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface CompositionLike {
|
|
17
|
+
title?: string
|
|
18
|
+
description?: string
|
|
19
|
+
tags?: unknown[]
|
|
20
|
+
icon?: string
|
|
21
|
+
slotLayout?: string
|
|
22
|
+
widgetsLayout?: unknown[]
|
|
23
|
+
status?: string
|
|
24
|
+
createdAt?: string
|
|
25
|
+
updatedAt?: string
|
|
26
|
+
[key: string]: unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Virtual composition sent to frontend (pathname.default matches route /:workSession/id=...) */
|
|
30
|
+
function buildVirtualComposition(
|
|
31
|
+
composition: CompositionLike,
|
|
32
|
+
workSession: WorkSessionLike,
|
|
33
|
+
): Record<string, unknown> {
|
|
34
|
+
const now = new Date()
|
|
35
|
+
return {
|
|
36
|
+
_id: uuidv4(),
|
|
37
|
+
title: composition.title ?? "Shared Session",
|
|
38
|
+
description:
|
|
39
|
+
composition.description ?? "Shared composition for anonymous access",
|
|
40
|
+
tags: composition.tags ?? [],
|
|
41
|
+
icon: composition.icon,
|
|
42
|
+
pathname: { default: "workSession" },
|
|
43
|
+
slotLayout: composition.slotLayout ?? "full",
|
|
44
|
+
widgetsLayout: composition.widgetsLayout ?? [],
|
|
45
|
+
status: composition.status ?? "live",
|
|
46
|
+
author: workSession.userId,
|
|
47
|
+
lastEditor: workSession.userId,
|
|
48
|
+
isDeleted: false,
|
|
49
|
+
createdAt: composition.createdAt ?? now.toISOString(),
|
|
50
|
+
updatedAt: composition.updatedAt ?? now.toISOString(),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Simulated interaction link so the frontend can resolve the composition like a normal project. */
|
|
55
|
+
function buildSimulatedInteractionLink(
|
|
56
|
+
workSession: WorkSessionLike,
|
|
57
|
+
virtualComposition: { _id: string; [key: string]: unknown },
|
|
58
|
+
): Record<string, unknown> {
|
|
59
|
+
const created = workSession.createdAt
|
|
60
|
+
? new Date(workSession.createdAt)
|
|
61
|
+
: new Date()
|
|
62
|
+
const updated = workSession.updatedAt
|
|
63
|
+
? new Date(workSession.updatedAt)
|
|
64
|
+
: new Date()
|
|
65
|
+
return {
|
|
66
|
+
_id: `session_${workSession._id}_interactionLink`,
|
|
67
|
+
composition: virtualComposition._id,
|
|
68
|
+
slotType: "page",
|
|
69
|
+
triggers: [],
|
|
70
|
+
queryParameters: [],
|
|
71
|
+
description: "Shared session composition",
|
|
72
|
+
tags: [],
|
|
73
|
+
author: workSession.userId,
|
|
74
|
+
lastEditor: workSession.userId,
|
|
75
|
+
isLandingPage: true,
|
|
76
|
+
isDeleted: false,
|
|
77
|
+
createdAt: created,
|
|
78
|
+
updatedAt: updated,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface WorkSessionJoinPayloadData {
|
|
83
|
+
compositions: unknown[]
|
|
84
|
+
interactionLinks: unknown[]
|
|
85
|
+
projectConfig: Record<string, unknown>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build the data part of joinConfirmation for a work session (no token).
|
|
90
|
+
* Socket manager adds token and decoded after creating the JWT.
|
|
91
|
+
*/
|
|
92
|
+
export function buildWorkSessionJoinPayload(
|
|
93
|
+
workSession: WorkSessionLike,
|
|
94
|
+
composition: CompositionLike,
|
|
95
|
+
): WorkSessionJoinPayloadData {
|
|
96
|
+
const virtualComposition = buildVirtualComposition(composition, workSession)
|
|
97
|
+
const simulatedInteractionLink = buildSimulatedInteractionLink(
|
|
98
|
+
workSession,
|
|
99
|
+
virtualComposition as { _id: string; [key: string]: unknown },
|
|
100
|
+
)
|
|
101
|
+
return {
|
|
102
|
+
compositions: [virtualComposition],
|
|
103
|
+
interactionLinks: [simulatedInteractionLink],
|
|
104
|
+
projectConfig: {},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -3,6 +3,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"
|
|
|
3
3
|
import { Type } from "@sinclair/typebox"
|
|
4
4
|
import type { PluginFastifyInstance } from "types"
|
|
5
5
|
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
7
|
+
|
|
6
8
|
const ParamsSchema = Type.Object({
|
|
7
9
|
_id: Type.String(),
|
|
8
10
|
})
|
|
@@ -39,7 +41,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
39
41
|
|
|
40
42
|
return {
|
|
41
43
|
handler,
|
|
42
|
-
onRequest: [fastify.authenticate],
|
|
44
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
43
45
|
config: {
|
|
44
46
|
type: "dataRead",
|
|
45
47
|
},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { FastifyReply, FastifyRequest } from "fastify"
|
|
2
2
|
import type { PluginFastifyInstance } from "types"
|
|
3
3
|
|
|
4
|
+
const permissions = ["user.isAdmin"]
|
|
5
|
+
|
|
4
6
|
export default (fastify: PluginFastifyInstance) => {
|
|
5
7
|
const accountService = fastify.custom.accountService
|
|
6
8
|
|
|
@@ -20,7 +22,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
20
22
|
|
|
21
23
|
return {
|
|
22
24
|
handler,
|
|
23
|
-
onRequest: [fastify.authenticate],
|
|
25
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
24
26
|
config: {
|
|
25
27
|
type: "dataRead",
|
|
26
28
|
},
|
|
@@ -2,7 +2,8 @@ import type { Static } from "@sinclair/typebox"
|
|
|
2
2
|
import type { FastifyReply, FastifyRequest } from "fastify"
|
|
3
3
|
import type { PluginFastifyInstance } from "types"
|
|
4
4
|
import { Type } from "@sinclair/typebox"
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
6
7
|
|
|
7
8
|
const ParamsSchema = Type.Object({
|
|
8
9
|
_id: Type.String(),
|
|
@@ -18,29 +19,25 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
18
19
|
}>,
|
|
19
20
|
reply: FastifyReply,
|
|
20
21
|
) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return reply.internalServerError(error.message)
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
throw new ForbiddenError("Only admins can hard delete this core datatype")
|
|
22
|
+
try {
|
|
23
|
+
const { _id } = req.params
|
|
24
|
+
|
|
25
|
+
const payload = await accountService.hardDeleteAccount(
|
|
26
|
+
fastify,
|
|
27
|
+
req.requestParams,
|
|
28
|
+
_id,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return payload
|
|
32
|
+
} catch (error: any) {
|
|
33
|
+
fastify.log.error(error.message)
|
|
34
|
+
return reply.internalServerError(error.message)
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
return {
|
|
42
39
|
handler,
|
|
43
|
-
onRequest: [fastify.authenticate],
|
|
40
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
44
41
|
config: {
|
|
45
42
|
type: "dataHardDeleted",
|
|
46
43
|
broadcastChannels: ["accountHardDeleted"],
|
|
@@ -5,6 +5,8 @@ import type { PluginFastifyInstance } from "types"
|
|
|
5
5
|
import { Type } from "@sinclair/typebox"
|
|
6
6
|
import { accountSchema } from "../account.schema"
|
|
7
7
|
|
|
8
|
+
const permissions = ["user.isAdmin"]
|
|
9
|
+
|
|
8
10
|
const ParamsSchema = Type.Object({
|
|
9
11
|
_id: Type.String(),
|
|
10
12
|
})
|
|
@@ -40,7 +42,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
40
42
|
|
|
41
43
|
return {
|
|
42
44
|
handler,
|
|
43
|
-
onRequest: [fastify.authenticate],
|
|
45
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
44
46
|
config: {
|
|
45
47
|
type: "dataUpdate",
|
|
46
48
|
broadcastChannels: ["accountUpdated", "broadcastDataUpdated"],
|
|
@@ -3,6 +3,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"
|
|
|
3
3
|
import type { PluginFastifyInstance } from "types"
|
|
4
4
|
import { accountSchema } from "../account.schema"
|
|
5
5
|
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
7
|
+
|
|
6
8
|
export default (fastify: PluginFastifyInstance) => {
|
|
7
9
|
const accountService = fastify.custom.accountService
|
|
8
10
|
|
|
@@ -29,7 +31,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
29
31
|
|
|
30
32
|
return {
|
|
31
33
|
handler,
|
|
32
|
-
onRequest: [fastify.authenticate],
|
|
34
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
33
35
|
config: {
|
|
34
36
|
type: "dataCreate",
|
|
35
37
|
broadcastChannels: ["accountCreated"],
|
|
@@ -3,6 +3,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"
|
|
|
3
3
|
import type { PluginFastifyInstance } from "types"
|
|
4
4
|
import { Type } from "@sinclair/typebox"
|
|
5
5
|
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
7
|
+
|
|
6
8
|
const ParamsSchema = Type.Object({
|
|
7
9
|
_id: Type.String(),
|
|
8
10
|
})
|
|
@@ -35,7 +37,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
35
37
|
|
|
36
38
|
return {
|
|
37
39
|
handler,
|
|
38
|
-
onRequest: [fastify.authenticate],
|
|
40
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
39
41
|
config: {
|
|
40
42
|
type: "dataDelete",
|
|
41
43
|
broadcastChannels: ["accountDeleted"],
|
|
@@ -94,11 +94,10 @@ export class CompositionService {
|
|
|
94
94
|
*/
|
|
95
95
|
async _readAllCompositions(
|
|
96
96
|
fastify: PluginFastifyInstance,
|
|
97
|
-
filters: Record<string, any> = {},
|
|
97
|
+
filters: Record<string, any> = { isDeleted: false },
|
|
98
98
|
): Promise<CompositionType[]> {
|
|
99
99
|
try {
|
|
100
|
-
|
|
101
|
-
filters.isDeleted = false
|
|
100
|
+
filters = { isDeleted: false, ...filters }
|
|
102
101
|
return await this.compositionModel
|
|
103
102
|
.find({
|
|
104
103
|
...filters,
|
|
@@ -337,6 +336,90 @@ export class CompositionService {
|
|
|
337
336
|
payload.body.deleted = [composition._id]
|
|
338
337
|
fastify.emit("compositionHardDeleted", payload)
|
|
339
338
|
|
|
339
|
+
return payload
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Core function to create multiple compositions (returns raw data)
|
|
343
|
+
*/
|
|
344
|
+
async _createCompositions(
|
|
345
|
+
fastify: PluginFastifyInstance,
|
|
346
|
+
compositionBodies: CompositionBody[],
|
|
347
|
+
userId: string,
|
|
348
|
+
): Promise<CompositionType[]> {
|
|
349
|
+
try {
|
|
350
|
+
const compositionsToCreate: CompositionBody[] = []
|
|
351
|
+
|
|
352
|
+
// Validate and prepare all compositions
|
|
353
|
+
for (const compositionBody of compositionBodies) {
|
|
354
|
+
if (compositionBody._id) {
|
|
355
|
+
const uuidValid = validate(compositionBody._id)
|
|
356
|
+
|
|
357
|
+
if (!uuidValid) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Invalid ID - not a valid uuid v4: ${compositionBody._id}`,
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const duplicate = await this.compositionModel.findOne({
|
|
364
|
+
_id: compositionBody._id,
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
if (duplicate) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`An entry with this id already exists: ${compositionBody._id}`,
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
compositionBody._id = uuidv4()
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
compositionsToCreate.push({
|
|
377
|
+
...compositionBody,
|
|
378
|
+
author: userId,
|
|
379
|
+
lastEditor: userId,
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Bulk insert
|
|
384
|
+
const compositions =
|
|
385
|
+
await this.compositionModel.insertMany(compositionsToCreate)
|
|
386
|
+
|
|
387
|
+
fastify.log.info(
|
|
388
|
+
`[API] Created ${compositions.length} compositions in bulk`,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return compositions.map((comp) =>
|
|
392
|
+
comp.toObject ? comp.toObject() : comp,
|
|
393
|
+
)
|
|
394
|
+
} catch (error: any) {
|
|
395
|
+
fastify.log.error(error.message)
|
|
396
|
+
throw error
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Create multiple compositions with payload wrapping and event emission
|
|
402
|
+
*/
|
|
403
|
+
async createCompositions(
|
|
404
|
+
fastify: PluginFastifyInstance,
|
|
405
|
+
requestData: FrontendPayloadRequestData,
|
|
406
|
+
compositionBodies: CompositionBody[],
|
|
407
|
+
userId: string,
|
|
408
|
+
): Promise<FrontendPayload<CompositionType[]>> {
|
|
409
|
+
const compositions = await this._createCompositions(
|
|
410
|
+
fastify,
|
|
411
|
+
compositionBodies,
|
|
412
|
+
userId,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
const payload = await createCompositionPayload(
|
|
416
|
+
fastify,
|
|
417
|
+
compositions,
|
|
418
|
+
requestData,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
fastify.emit("compositionsCreated", payload)
|
|
422
|
+
|
|
340
423
|
return payload
|
|
341
424
|
}
|
|
342
425
|
}
|
|
@@ -4,6 +4,7 @@ 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"
|
|
8
9
|
import restore from "./route.composition.restore"
|
|
9
10
|
|
|
@@ -13,6 +14,7 @@ const registerRoutes = async (fastify: PluginFastifyInstance) => {
|
|
|
13
14
|
await fastify.patch("/composition/:_id", patch(fastify))
|
|
14
15
|
await fastify.patch("/composition/:_id/restore", restore(fastify))
|
|
15
16
|
await fastify.post("/composition", post(fastify))
|
|
17
|
+
await fastify.post("/composition/bulk", postBulk(fastify))
|
|
16
18
|
await fastify.delete("/composition/:_id", remove(fastify))
|
|
17
19
|
await fastify.delete("/composition/:_id/hard", hardDelete(fastify))
|
|
18
20
|
}
|
|
@@ -3,6 +3,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"
|
|
|
3
3
|
import type { PluginFastifyInstance } from "types"
|
|
4
4
|
import { Type } from "@sinclair/typebox"
|
|
5
5
|
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
7
|
+
|
|
6
8
|
const ParamsSchema = Type.Object({
|
|
7
9
|
_id: Type.String(),
|
|
8
10
|
})
|
|
@@ -39,7 +41,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
39
41
|
|
|
40
42
|
return {
|
|
41
43
|
handler,
|
|
42
|
-
onRequest: [fastify.authenticate],
|
|
44
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
43
45
|
config: {
|
|
44
46
|
type: "dataRead",
|
|
45
47
|
},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { FastifyReply, FastifyRequest } from "fastify"
|
|
2
2
|
import type { PluginFastifyInstance } from "types"
|
|
3
3
|
|
|
4
|
+
const permissions = ["user.isAdmin"]
|
|
5
|
+
|
|
4
6
|
export default (fastify: PluginFastifyInstance) => {
|
|
5
7
|
const compositionService = fastify.custom.compositionService
|
|
6
8
|
|
|
@@ -21,7 +23,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
21
23
|
|
|
22
24
|
return {
|
|
23
25
|
handler,
|
|
24
|
-
onRequest: [fastify.authenticate],
|
|
26
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
25
27
|
config: {
|
|
26
28
|
type: "dataRead",
|
|
27
29
|
},
|
|
@@ -2,7 +2,8 @@ import type { Static } from "@sinclair/typebox"
|
|
|
2
2
|
import type { FastifyReply, FastifyRequest } from "fastify"
|
|
3
3
|
import type { PluginFastifyInstance } from "types"
|
|
4
4
|
import { Type } from "@sinclair/typebox"
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
6
7
|
|
|
7
8
|
const ParamsSchema = Type.Object({
|
|
8
9
|
_id: Type.String(),
|
|
@@ -18,29 +19,25 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
18
19
|
}>,
|
|
19
20
|
reply: FastifyReply,
|
|
20
21
|
) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return reply.internalServerError(error.message)
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
throw new ForbiddenError("Only admins can hard delete this core datatype")
|
|
22
|
+
try {
|
|
23
|
+
const { _id } = req.params
|
|
24
|
+
|
|
25
|
+
const payload = await compositionService.hardDeleteComposition(
|
|
26
|
+
fastify,
|
|
27
|
+
req.requestParams,
|
|
28
|
+
_id,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return payload
|
|
32
|
+
} catch (error: any) {
|
|
33
|
+
fastify.log.error(error.message)
|
|
34
|
+
return reply.internalServerError(error.message)
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
return {
|
|
42
39
|
handler,
|
|
43
|
-
onRequest: [fastify.authenticate],
|
|
40
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
44
41
|
config: {
|
|
45
42
|
type: "dataHardDelete",
|
|
46
43
|
broadcastChannels: ["compositionHardDeleted"],
|
|
@@ -5,6 +5,8 @@ import type { PluginFastifyInstance } from "types"
|
|
|
5
5
|
import { Type } from "@sinclair/typebox"
|
|
6
6
|
import { compositionUpdateSchema } from "../composition.schema"
|
|
7
7
|
|
|
8
|
+
const permissions = ["user.isAdmin"]
|
|
9
|
+
|
|
8
10
|
const ParamsSchema = Type.Object({
|
|
9
11
|
_id: Type.String(),
|
|
10
12
|
})
|
|
@@ -42,7 +44,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
42
44
|
|
|
43
45
|
return {
|
|
44
46
|
handler,
|
|
45
|
-
onRequest: [fastify.authenticate],
|
|
47
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
46
48
|
config: {
|
|
47
49
|
type: "dataUpdate",
|
|
48
50
|
broadcastChannels: ["compositionUpdated"],
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
const permissions = ["user.isAdmin"]
|
|
7
|
+
|
|
8
|
+
export default (fastify: PluginFastifyInstance) => {
|
|
9
|
+
const compositionService = fastify.custom.compositionService
|
|
10
|
+
|
|
11
|
+
const handler = async (
|
|
12
|
+
req: FastifyRequest<{
|
|
13
|
+
Body: CompositionBody[]
|
|
14
|
+
}>,
|
|
15
|
+
reply: FastifyReply,
|
|
16
|
+
) => {
|
|
17
|
+
try {
|
|
18
|
+
// Validate that body is an array
|
|
19
|
+
if (!Array.isArray(req.body)) {
|
|
20
|
+
return reply.badRequest("Request body must be an array of compositions")
|
|
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 compositionService.createCompositions(
|
|
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, fastify.checkPermissions(permissions)],
|
|
46
|
+
config: {
|
|
47
|
+
type: "dataCreate",
|
|
48
|
+
broadcastChannels: ["compositionsCreated"],
|
|
49
|
+
},
|
|
50
|
+
schema: {
|
|
51
|
+
summary: "Create multiple compositions in bulk",
|
|
52
|
+
tags: ["core/composition"],
|
|
53
|
+
body: {
|
|
54
|
+
type: "array",
|
|
55
|
+
items: compositionCreateSchema,
|
|
56
|
+
minItems: 1,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -3,6 +3,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"
|
|
|
3
3
|
import type { PluginFastifyInstance } from "types"
|
|
4
4
|
import { compositionCreateSchema } from "../composition.schema"
|
|
5
5
|
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
7
|
+
|
|
6
8
|
export default (fastify: PluginFastifyInstance) => {
|
|
7
9
|
const compositionService = fastify.custom.compositionService
|
|
8
10
|
|
|
@@ -30,7 +32,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
30
32
|
|
|
31
33
|
return {
|
|
32
34
|
handler,
|
|
33
|
-
onRequest: [fastify.authenticate],
|
|
35
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
34
36
|
config: {
|
|
35
37
|
type: "dataCreate",
|
|
36
38
|
broadcastChannels: ["compositionCreated"],
|
|
@@ -3,6 +3,8 @@ import type { FastifyReply, FastifyRequest } from "fastify"
|
|
|
3
3
|
import type { PluginFastifyInstance } from "types"
|
|
4
4
|
import { Type } from "@sinclair/typebox"
|
|
5
5
|
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
7
|
+
|
|
6
8
|
const ParamsSchema = Type.Object({
|
|
7
9
|
_id: Type.String(),
|
|
8
10
|
})
|
|
@@ -35,7 +37,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
35
37
|
|
|
36
38
|
return {
|
|
37
39
|
handler,
|
|
38
|
-
onRequest: [fastify.authenticate],
|
|
40
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
39
41
|
config: {
|
|
40
42
|
type: "dataDelete",
|
|
41
43
|
broadcastChannels: ["compositionDeleted"],
|
|
@@ -2,7 +2,8 @@ import type { Static } from "@sinclair/typebox"
|
|
|
2
2
|
import type { FastifyReply, FastifyRequest } from "fastify"
|
|
3
3
|
import { PluginFastifyInstance } from "types"
|
|
4
4
|
import { Type } from "@sinclair/typebox"
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const permissions = ["user.isAdmin"]
|
|
6
7
|
|
|
7
8
|
const ParamsSchema = Type.Object({
|
|
8
9
|
_id: Type.String(),
|
|
@@ -17,21 +18,17 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
17
18
|
reply: FastifyReply,
|
|
18
19
|
) => {
|
|
19
20
|
try {
|
|
20
|
-
|
|
21
|
-
const { _id } = req.params
|
|
21
|
+
const { _id } = req.params
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
const payload = await compositionService.updateComposition(
|
|
24
|
+
fastify,
|
|
25
|
+
req.requestParams,
|
|
26
|
+
_id,
|
|
27
|
+
{ isDeleted: false },
|
|
28
|
+
req.user._id,
|
|
29
|
+
)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
} else {
|
|
33
|
-
throw new ForbiddenError("Only admins can restore this core datatype")
|
|
34
|
-
}
|
|
31
|
+
return payload
|
|
35
32
|
} catch (error: any) {
|
|
36
33
|
fastify.log.error(error.message)
|
|
37
34
|
return reply.internalServerError(error.message)
|
|
@@ -40,7 +37,7 @@ export default (fastify: PluginFastifyInstance) => {
|
|
|
40
37
|
|
|
41
38
|
return {
|
|
42
39
|
handler,
|
|
43
|
-
onRequest: [fastify.authenticate],
|
|
40
|
+
onRequest: [fastify.authenticate, fastify.checkPermissions(permissions)],
|
|
44
41
|
config: {
|
|
45
42
|
type: "dataRestore",
|
|
46
43
|
broadcastChannels: ["compositionRestored"],
|