@raclettejs/core 0.1.10 → 0.1.12

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 (96) hide show
  1. package/CHANGELOG.md +59 -2
  2. package/dist/cli.js +104 -104
  3. package/dist/cli.js.map +7 -0
  4. package/dist/index.js +4 -4
  5. package/dist/index.js.map +7 -0
  6. package/package.json +32 -31
  7. package/services/backend/package.json +35 -45
  8. package/services/backend/src/core/payload/payloadTypes.ts +7 -0
  9. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.get.ts +1 -1
  10. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.getAll.ts +1 -1
  11. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.model.ts +5 -0
  12. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.schema.ts +1 -0
  13. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +1 -1
  14. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.get.ts +1 -1
  15. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.getAll.ts +1 -1
  16. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/helpers/payload.ts +6 -2
  17. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.get.ts +1 -1
  18. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.getAll.ts +1 -1
  19. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.get.ts +1 -1
  20. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.getAll.ts +1 -1
  21. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/index.ts +2 -1
  22. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.get.ts +1 -1
  23. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.getAll.ts +6 -2
  24. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +22 -1
  25. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.get.ts +1 -1
  26. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.getAll.ts +1 -1
  27. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.remove.ts +3 -0
  28. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.service.ts +1 -2
  29. package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +12 -12
  30. package/services/backend/src/domains/index.ts +3 -0
  31. package/services/backend/src/domains/system/collections.service.ts +119 -0
  32. package/services/backend/src/domains/system/index.ts +8 -0
  33. package/services/backend/src/domains/system/routes/index.ts +5 -0
  34. package/services/backend/src/domains/system/routes/route.collection.getAll.ts +61 -0
  35. package/services/backend/src/domains/system/routes/route.collection.remove.ts +93 -0
  36. package/services/backend/src/types/custom-fastify.d.ts +1 -0
  37. package/services/backend/src/utils/request.utils.ts +28 -1
  38. package/services/backend/yarn.lock +431 -1180
  39. package/services/frontend/package.json +29 -40
  40. package/services/frontend/src/core/constants/ApiClient.ts +6 -1
  41. package/services/frontend/src/core/lib/data/dataApi.ts +8 -1
  42. package/services/frontend/src/core/lib/data/fetchDataHandler.ts +58 -27
  43. package/services/frontend/src/core/lib/data/queryApi.ts +29 -8
  44. package/services/frontend/src/core/lib/data/responseTypeHandler.ts +27 -4
  45. package/services/frontend/src/core/lib/data/writeDataHandler.ts +1 -1
  46. package/services/frontend/src/core/lib/dataHelper.ts +8 -1
  47. package/services/frontend/src/core/lib/easteregg.ts +1 -1
  48. package/services/frontend/src/core/lib/eventEmitter.ts +1 -0
  49. package/services/frontend/src/core/lib/eventWhitelist.ts +11 -1
  50. package/services/frontend/src/core/lib/logger.ts +11 -3
  51. package/services/frontend/src/core/lib/userNotifier.ts +17 -17
  52. package/services/frontend/src/core/main.ts +3 -3
  53. package/services/frontend/src/core/setup/plugin-system/api/wrappers.ts +18 -1
  54. package/services/frontend/src/core/setup/plugin-system/registration/index.ts +1 -1
  55. package/services/frontend/src/core/setup/socketClient.ts +2 -2
  56. package/services/frontend/src/core/setup/socketEvents.ts +2 -2
  57. package/services/frontend/src/core/store/reducers/compositions/selectors.ts +2 -2
  58. package/services/frontend/src/core/store/reducers/data/index.ts +1 -1
  59. package/services/frontend/src/core/store/reducers/data/reducers.ts +3 -3
  60. package/services/frontend/src/core/store/reducers/queries/effects.ts +20 -1
  61. package/services/frontend/src/core/store/reducers/queries/index.ts +1 -0
  62. package/services/frontend/src/core/store/reducers/queries/reducers.ts +28 -1
  63. package/services/frontend/src/core/store/reducers/queriesCache/queryCacheHelper.ts +5 -4
  64. package/services/frontend/src/core/store/reducers/queriesCache/reducers.ts +4 -3
  65. package/services/frontend/src/core/store/state.ts +1 -1
  66. package/services/frontend/src/core/types/ApiClient.ts +1 -1
  67. package/services/frontend/src/core/types/DataApi.ts +4 -0
  68. package/services/frontend/src/core/types/Queries.ts +1 -0
  69. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +19 -7
  70. package/services/frontend/src/orchestrator/WelcomeScreen.vue +39 -41
  71. package/services/frontend/src/orchestrator/assets/styles/tailwindStyles.css +3 -0
  72. package/services/frontend/src/orchestrator/assets/styles/themes/dark.ts +2 -2
  73. package/services/frontend/src/orchestrator/assets/styles/vuetifyStyles.scss +0 -1
  74. package/services/frontend/src/orchestrator/components/composition/CompositionOverlay.vue +11 -2
  75. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +167 -58
  76. package/services/frontend/src/orchestrator/components/menu/DevIndicator.vue +122 -7
  77. package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +38 -20
  78. package/services/frontend/src/orchestrator/components/snackbar/SnackStack.vue +17 -5
  79. package/services/frontend/src/orchestrator/composables/useCurrentComposition.ts +32 -4
  80. package/services/frontend/src/orchestrator/composables/usePageNavigation.ts +5 -1
  81. package/services/frontend/src/orchestrator/composables/usePluginApi.ts +125 -26
  82. package/services/frontend/src/orchestrator/composables/useRouteState.ts +0 -1
  83. package/services/frontend/src/orchestrator/composables/useVueQueryObservableHelper.ts +2 -0
  84. package/services/frontend/src/orchestrator/composables/useVueWriteOperationHelper.ts +5 -3
  85. package/services/frontend/src/orchestrator/composables/useWidgets/helperFunctions.ts +4 -1
  86. package/services/frontend/src/orchestrator/helpers/loginUserNotifications.ts +1 -1
  87. package/services/frontend/src/orchestrator/helpers/uiHelper.ts +1 -1
  88. package/services/frontend/src/orchestrator/i18n/de-DE.json +2 -1
  89. package/services/frontend/src/orchestrator/i18n/en-EU.json +2 -1
  90. package/services/frontend/src/orchestrator/i18n/sk.json +2 -1
  91. package/services/frontend/src/orchestrator/i18n/tl-TL.json +1 -1
  92. package/services/frontend/src/orchestrator/router/routeParserHelper.ts +4 -1
  93. package/services/frontend/src/orchestrator/router/routeStore.ts +78 -11
  94. package/services/frontend/tsconfig.app.json +1 -0
  95. package/services/frontend/yarn.lock +796 -1872
  96. package/yarn.lock +435 -618
@@ -36,6 +36,7 @@ const baseCompositionSchema = {
36
36
  tags: Type.Array(Type.String(), { default: [] }),
37
37
  icon: Type.Optional(Type.String()),
38
38
  pathname: Type.Record(Type.String(), Type.String(), { default: {} }),
39
+ slotLayout: Type.Optional(Type.String()),
39
40
  status: Type.Enum(
40
41
  { live: "live", test: "test", inProgress: "inProgress" },
41
42
  { default: "inProgress" },
@@ -4,9 +4,9 @@ import type {
4
4
  FrontendPayloadRequestData,
5
5
  } from "@c/payload/payloadTypes"
6
6
  import type { PluginFastifyInstance } from "types"
7
+ import type { Model } from "mongoose"
7
8
  import { v4 as uuidv4, validate } from "uuid"
8
9
  import { createCompositionPayload } from "./helpers/payload"
9
- import { Model } from "mongoose"
10
10
 
11
11
  export type CompositionBody = Partial<Omit<CompositionType, "_id">> & {
12
12
  _id?: string
@@ -41,7 +41,7 @@ export default (fastify: PluginFastifyInstance) => {
41
41
  handler,
42
42
  onRequest: [fastify.authenticate],
43
43
  config: {
44
- type: "dataPush",
44
+ type: "dataRead",
45
45
  },
46
46
  schema: {
47
47
  summary: "Get a composition by id",
@@ -22,7 +22,7 @@ export default (fastify: PluginFastifyInstance) => {
22
22
  handler,
23
23
  onRequest: [fastify.authenticate],
24
24
  config: {
25
- type: "dataPush",
25
+ type: "dataRead",
26
26
  },
27
27
  schema: {
28
28
  summary: "Get all compositions",
@@ -5,9 +5,13 @@ export const registerPayload = (fastify: PluginFastifyInstance) => {
5
5
  fastify.registerPayloadHandler<InteractionLink>("interactionLink", {
6
6
  type: "interactionLink",
7
7
  displayName: (interactionLink: InteractionLink) =>
8
- interactionLink.description || "",
8
+ interactionLink.description ||
9
+ `CompositionType: ${interactionLink.slotType}` ||
10
+ "No Description",
9
11
  completion: (interactionLink: InteractionLink) =>
10
- interactionLink.description || "",
12
+ interactionLink.description ||
13
+ interactionLink.slotType ||
14
+ "No Description",
11
15
  fields: (item: InteractionLink) => ({
12
16
  owner: item.author,
13
17
  }),
@@ -41,7 +41,7 @@ export default (fastify: PluginFastifyInstance) => {
41
41
  handler,
42
42
  onRequest: [fastify.authenticate],
43
43
  config: {
44
- type: "dataPush",
44
+ type: "dataRead",
45
45
  },
46
46
  schema: {
47
47
  summary: "Get a interactionLink by id",
@@ -22,7 +22,7 @@ export default (fastify: PluginFastifyInstance) => {
22
22
  handler,
23
23
  onRequest: [fastify.authenticate],
24
24
  config: {
25
- type: "dataPush",
25
+ type: "dataRead",
26
26
  },
27
27
  schema: {
28
28
  summary: "Get all interactionLinks",
@@ -41,7 +41,7 @@ export default (fastify: PluginFastifyInstance) => {
41
41
  handler,
42
42
  onRequest: [fastify.authenticate],
43
43
  config: {
44
- type: "dataPush",
44
+ type: "dataRead",
45
45
  },
46
46
  schema: {
47
47
  summary: "Get a project by id",
@@ -26,7 +26,7 @@ export default (fastify: PluginFastifyInstance) => {
26
26
  handler,
27
27
  onRequest: [fastify.authenticate],
28
28
  config: {
29
- type: "dataPush",
29
+ type: "dataRead",
30
30
  },
31
31
  schema: {
32
32
  summary: "Get all projects",
@@ -1,4 +1,4 @@
1
- import { PluginFastifyInstance } from "types"
1
+ import type { PluginFastifyInstance } from "types"
2
2
  import registerRoutes from "./routes"
3
3
  import { createModels } from "./tag.model"
4
4
  import { createTagService } from "./tag.service"
@@ -41,6 +41,7 @@ const registerTag = async (fastify: PluginFastifyInstance) => {
41
41
  operations: {
42
42
  readAll: tagService._readTags.bind(tagService),
43
43
  read: tagService._readTag.bind(tagService),
44
+ readByTitle: tagService._readTagByTitle.bind(tagService),
44
45
  create: tagService._createTag.bind(tagService),
45
46
  update: tagService._updateTag.bind(tagService),
46
47
  delete: tagService._removeTag.bind(tagService),
@@ -30,7 +30,7 @@ export default (fastify: PluginFastifyInstance) => {
30
30
  handler,
31
31
  onRequest: [fastify.authenticate],
32
32
  config: {
33
- type: "dataPush",
33
+ type: "dataRead",
34
34
  },
35
35
  schema: {
36
36
  summary: "Get a tag by id.",
@@ -6,7 +6,11 @@ export default (fastify: PluginFastifyInstance) => {
6
6
 
7
7
  const handler = async (req: FastifyRequest, reply: FastifyReply) => {
8
8
  try {
9
- const payload = await tagService.readTags(fastify, req.requestParams)
9
+ const payload = await tagService.readTags(
10
+ fastify,
11
+ req.requestParams,
12
+ req.query,
13
+ )
10
14
 
11
15
  if (!payload || payload.length === 0) {
12
16
  return reply.code(204).send()
@@ -24,7 +28,7 @@ export default (fastify: PluginFastifyInstance) => {
24
28
  handler,
25
29
  onRequest: [fastify.authenticate],
26
30
  config: {
27
- type: "dataPush",
31
+ type: "dataRead",
28
32
  },
29
33
  schema: {
30
34
  summary: "Get all tags for the given project.",
@@ -101,6 +101,27 @@ export class TagService {
101
101
  return createTagPayload(fastify, [tag], requestData)
102
102
  }
103
103
 
104
+ /**
105
+ * Core function to read a tag by name – used for internal system functions mainly
106
+ */
107
+ async _readTagByTitle(
108
+ fastify: PluginFastifyInstance,
109
+ title: string,
110
+ ): Promise<TagType> {
111
+ try {
112
+ const tags = await this.tagModel.find({ title }).lean()
113
+
114
+ if (!tags) {
115
+ throw new Error(`Tag with title ${title} not found`)
116
+ }
117
+
118
+ return tags[0]
119
+ } catch (error: any) {
120
+ fastify.log.error(error.message)
121
+ throw error
122
+ }
123
+ }
124
+
104
125
  /**
105
126
  * Core function to read tags by ID or filter parameters (returns raw data)
106
127
  */
@@ -153,7 +174,7 @@ export class TagService {
153
174
 
154
175
  return createTagPayload(fastify, [tag], requestData)
155
176
  } else {
156
- const tags = await this._readTags(fastify, filter)
177
+ const tags = await this._readTags(fastify, filter, requestData.options)
157
178
 
158
179
  return createTagPayload(fastify, tags, requestData)
159
180
  }
@@ -39,7 +39,7 @@ export default (fastify: PluginFastifyInstance) => {
39
39
  handler,
40
40
  onRequest: [fastify.authenticate],
41
41
  config: {
42
- type: "dataPush",
42
+ type: "dataRead",
43
43
  },
44
44
  schema: {
45
45
  summary: "Get user",
@@ -39,7 +39,7 @@ export default (fastify: PluginFastifyInstance) => {
39
39
  handler,
40
40
  onRequest: [fastify.authenticate],
41
41
  config: {
42
- type: "dataPush",
42
+ type: "dataRead",
43
43
  },
44
44
  schema: {
45
45
  summary: "Get users",
@@ -19,6 +19,9 @@ export default (fastify: PluginFastifyInstance) => {
19
19
  reply: FastifyReply,
20
20
  ) => {
21
21
  try {
22
+ if (!req.user.isAdmin) {
23
+ throw new ForbiddenError("Only Admins can use this feature")
24
+ }
22
25
  if (req.user._id === req.params._id) {
23
26
  throw new ForbiddenError("A user cannot remove itself")
24
27
  }
@@ -168,9 +168,8 @@ export class UserService {
168
168
  fastify: PluginFastifyInstance,
169
169
  requestData: FrontendPayloadRequestData,
170
170
  filter: Record<string, any> = {},
171
- options: QueryOptions = {},
172
171
  ): Promise<FrontendPayload<PublicUser[]>> {
173
- const users = await this._readUsers(fastify, filter, options)
172
+ const users = await this._readUsers(fastify, filter, requestData.options)
174
173
 
175
174
  return createUserPayload(fastify, users, requestData)
176
175
  }
@@ -15,12 +15,12 @@ export default {
15
15
  getAll: {
16
16
  target: "/raclette__core/account/all",
17
17
  method: "get",
18
- storeActionType: "dataPush"
18
+ storeActionType: "dataRead"
19
19
  },
20
20
  get: {
21
21
  target: (payload) => `/raclette__core/account/${payload._id}`,
22
22
  method: "get",
23
- storeActionType: "dataPush"
23
+ storeActionType: "dataRead"
24
24
  },
25
25
  update: {
26
26
  target: (payload) => `/raclette__core/account/${payload._id}`,
@@ -83,12 +83,12 @@ export default {
83
83
  getAll: {
84
84
  target: "/raclette__core/interactionLink/all",
85
85
  method: "get",
86
- storeActionType: "dataPush"
86
+ storeActionType: "dataRead"
87
87
  },
88
88
  get: {
89
89
  target: (payload) => `/raclette__core/interactionLink/${payload._id}`,
90
90
  method: "get",
91
- storeActionType: "dataPush"
91
+ storeActionType: "dataRead"
92
92
  },
93
93
  update: {
94
94
  target: (payload) => `/raclette__core/interactionLink/${payload._id}`,
@@ -146,12 +146,12 @@ export default {
146
146
  getAll: {
147
147
  target: "/raclette__core/project/all",
148
148
  method: "get",
149
- storeActionType: "dataPush"
149
+ storeActionType: "dataRead"
150
150
  },
151
151
  get: {
152
152
  target: (payload) => `/raclette__core/project/${payload._id}`,
153
153
  method: "get",
154
- storeActionType: "dataPush"
154
+ storeActionType: "dataRead"
155
155
  },
156
156
  create: {
157
157
  target: "/raclette__core/project",
@@ -209,12 +209,12 @@ export default {
209
209
  getAll: {
210
210
  target: "/raclette__core/tag/all",
211
211
  method: "get",
212
- storeActionType: "dataPush"
212
+ storeActionType: "dataRead"
213
213
  },
214
214
  get: {
215
215
  target: (payload) => `/raclette__core/tag/${payload._id}`,
216
216
  method: "get",
217
- storeActionType: "dataPush"
217
+ storeActionType: "dataRead"
218
218
  },
219
219
  update: {
220
220
  target: (payload) => `/raclette__core/tag/${payload._id}`,
@@ -272,12 +272,12 @@ export default {
272
272
  getAll: {
273
273
  target: "/raclette__core/user/all",
274
274
  method: "get",
275
- storeActionType: "dataPush"
275
+ storeActionType: "dataRead"
276
276
  },
277
277
  get: {
278
278
  target: (payload) => `/raclette__core/user/${payload._id}`,
279
279
  method: "get",
280
- storeActionType: "dataPush"
280
+ storeActionType: "dataRead"
281
281
  },
282
282
  update: {
283
283
  target: (payload) => `/raclette__core/user/${payload._id}`,
@@ -335,12 +335,12 @@ export default {
335
335
  getAll: {
336
336
  target: "/raclette__core/composition/all",
337
337
  method: "get",
338
- storeActionType: "dataPush"
338
+ storeActionType: "dataRead"
339
339
  },
340
340
  get: {
341
341
  target: (payload) => `/raclette__core/composition/${payload._id}`,
342
342
  method: "get",
343
- storeActionType: "dataPush"
343
+ storeActionType: "dataRead"
344
344
  },
345
345
  update: {
346
346
  target: (payload) => `/raclette__core/composition/${payload._id}`,
@@ -6,6 +6,9 @@ import registerSystem from "./system"
6
6
  * Core service export all routes
7
7
  */
8
8
  const registerCore = async (fastify: FastifyInstance) => {
9
+ if (!fastify.core) {
10
+ fastify.core = {}
11
+ }
9
12
  registerSystem(fastify)
10
13
  }
11
14
 
@@ -0,0 +1,119 @@
1
+ import type { PluginFastifyInstance } from "types"
2
+ import mongoose, { type Connection } from "mongoose"
3
+
4
+ export interface CollectionInfo {
5
+ name: string
6
+ count: number
7
+ size: number
8
+ indexes: number
9
+ }
10
+
11
+ export class CollectionService {
12
+ private connection: Connection
13
+
14
+ constructor(connection: Connection) {
15
+ this.connection = connection
16
+ }
17
+
18
+ /**
19
+ * Get all collection names and their metadata
20
+ */
21
+ async getCollections(
22
+ fastify: PluginFastifyInstance,
23
+ ): Promise<CollectionInfo[]> {
24
+ try {
25
+ const db = this.connection.db
26
+ const collections = await db.listCollections().toArray()
27
+
28
+ const collectionInfos: CollectionInfo[] = []
29
+
30
+ for (const collection of collections) {
31
+ const collectionName = collection.name
32
+
33
+ // Get document count
34
+ const count = await db.collection(collectionName).countDocuments()
35
+
36
+ // Get collection stats using MongoDB command
37
+ const stats = await db.command({ collStats: collectionName })
38
+
39
+ collectionInfos.push({
40
+ name: collectionName,
41
+ count: count || 0,
42
+ size: stats.size || 0,
43
+ indexes: stats.nindexes || 0,
44
+ })
45
+ }
46
+
47
+ fastify.log.info(`[API] Retrieved ${collectionInfos.length} collections`)
48
+
49
+ return collectionInfos
50
+ } catch (error: any) {
51
+ fastify.log.error(error.message)
52
+ throw error
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Drop specific collections
58
+ */
59
+ async dropCollections(
60
+ fastify: PluginFastifyInstance,
61
+ collectionNames: string[],
62
+ ): Promise<{ dropped: string[]; failed: string[] }> {
63
+ try {
64
+ const db = this.connection.db
65
+ const dropped: string[] = []
66
+ const failed: string[] = []
67
+
68
+ for (const collectionName of collectionNames) {
69
+ try {
70
+ // Check if collection exists
71
+ const collections = await db
72
+ .listCollections({ name: collectionName })
73
+ .toArray()
74
+
75
+ if (collections.length === 0) {
76
+ fastify.log.warn(`Collection ${collectionName} does not exist`)
77
+ failed.push(collectionName)
78
+ continue
79
+ }
80
+
81
+ // Drop the collection
82
+ await db.collection(collectionName).drop()
83
+ dropped.push(collectionName)
84
+ fastify.log.info(`[API] Dropped collection: ${collectionName}`)
85
+ } catch (error: any) {
86
+ fastify.log.error(
87
+ `Failed to drop collection ${collectionName}: ${error.message}`,
88
+ )
89
+ failed.push(collectionName)
90
+ }
91
+ }
92
+
93
+ return { dropped, failed }
94
+ } catch (error: any) {
95
+ fastify.log.error(error.message)
96
+ throw error
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Drop all collections (use with extreme caution!)
102
+ */
103
+ async dropAllCollections(
104
+ fastify: PluginFastifyInstance,
105
+ ): Promise<{ dropped: string[]; failed: string[] }> {
106
+ try {
107
+ const collections = await this.getCollections(fastify)
108
+ const collectionNames = collections.map((c) => c.name)
109
+
110
+ return await this.dropCollections(fastify, collectionNames)
111
+ } catch (error: any) {
112
+ fastify.log.error(error.message)
113
+ throw error
114
+ }
115
+ }
116
+ }
117
+
118
+ export const createCollectionService = (connection: Connection) =>
119
+ new CollectionService(connection)
@@ -1,8 +1,16 @@
1
1
  import type { FastifyInstance } from "fastify"
2
+ import mongoose from "mongoose"
2
3
  import registerRoutes from "./routes"
4
+ import { createCollectionService } from "./collections.service"
3
5
 
4
6
  const registerSystem = (fastify: FastifyInstance) => {
5
7
  registerRoutes(fastify)
8
+
9
+ // create and pass in the model to your service
10
+ const collectionService = createCollectionService(mongoose.connection)
11
+
12
+ // decorate the service to the instance, so we can easily call it everywhere
13
+ fastify.core.collectionService = collectionService
6
14
  }
7
15
 
8
16
  export default registerSystem
@@ -7,6 +7,8 @@ import {
7
7
  import deploy from "./route.deploy"
8
8
  import login from "./route.user.login"
9
9
  import checkToken from "./route.user.checkToken"
10
+ import getAllCollections from "./route.collection.getAll"
11
+ import deleteCollections from "./route.collection.remove"
10
12
 
11
13
  const registerRoutes = async (fastify: FastifyInstance) => {
12
14
  await fastify.get("/system/deploy", deploy(fastify))
@@ -17,5 +19,8 @@ const registerRoutes = async (fastify: FastifyInstance) => {
17
19
  await fastify.get("/admin/cache/blocks", getBlockedLogins(fastify))
18
20
  await fastify.delete("/admin/cache/blocks/:key", resetLoginBlock(fastify))
19
21
  await fastify.delete("/admin/cache/blocks", resetAllLoginBlocks(fastify))
22
+
23
+ await fastify.get("/admin/collections/all", getAllCollections(fastify))
24
+ await fastify.delete("/admin/collections", deleteCollections(fastify))
20
25
  }
21
26
  export default registerRoutes
@@ -0,0 +1,61 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify"
2
+ import { PluginFastifyInstance } from "types"
3
+ import { ForbiddenError } from "@/utils/errors"
4
+
5
+ export default (fastify: PluginFastifyInstance) => {
6
+ const collectionService = fastify.core.collectionService
7
+
8
+ const handler = async (req: FastifyRequest, reply: FastifyReply) => {
9
+ try {
10
+ // Restrict to admin users only
11
+ if (!req.user.isAdmin) {
12
+ throw new ForbiddenError(
13
+ "Only Admins can access collection information",
14
+ )
15
+ }
16
+
17
+ const collections = await collectionService.getCollections(fastify)
18
+
19
+ return {
20
+ success: true,
21
+ data: collections,
22
+ total: collections.length,
23
+ }
24
+ } catch (error: any) {
25
+ fastify.log.error(error.message)
26
+ return reply.internalServerError(error.message)
27
+ }
28
+ }
29
+
30
+ return {
31
+ handler,
32
+ onRequest: [fastify.authenticate],
33
+ schema: {
34
+ summary: "Get all collections",
35
+ description:
36
+ "Retrieve all MongoDB collections with metadata (admin only, debug feature)",
37
+ tags: ["debug/collections"],
38
+ response: {
39
+ 200: {
40
+ type: "object",
41
+ properties: {
42
+ success: { type: "boolean" },
43
+ data: {
44
+ type: "array",
45
+ items: {
46
+ type: "object",
47
+ properties: {
48
+ name: { type: "string" },
49
+ count: { type: "number" },
50
+ size: { type: "number" },
51
+ indexes: { type: "number" },
52
+ },
53
+ },
54
+ },
55
+ total: { type: "number" },
56
+ },
57
+ },
58
+ },
59
+ },
60
+ }
61
+ }
@@ -0,0 +1,93 @@
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 BodySchema = Type.Object({
8
+ collections: Type.Array(Type.String(), {
9
+ description: "Array of collection names to drop",
10
+ minItems: 1,
11
+ }),
12
+ dropAll: Type.Optional(
13
+ Type.Boolean({
14
+ description:
15
+ "If true, drops all collections (overrides collections array)",
16
+ }),
17
+ ),
18
+ })
19
+ type Body = Static<typeof BodySchema>
20
+
21
+ export default (fastify: PluginFastifyInstance) => {
22
+ const collectionService = fastify.core.collectionService
23
+
24
+ const handler = async (
25
+ req: FastifyRequest<{
26
+ Body: Body
27
+ }>,
28
+ reply: FastifyReply,
29
+ ) => {
30
+ try {
31
+ // Restrict to admin users only
32
+ if (!req.user.isAdmin) {
33
+ throw new ForbiddenError("Only Admins can drop collections")
34
+ }
35
+
36
+ let result
37
+
38
+ if (req.body.dropAll) {
39
+ fastify.log.warn(
40
+ `[API] Admin ${req.user._id} is dropping ALL collections`,
41
+ )
42
+ result = await collectionService.dropAllCollections(fastify)
43
+ } else {
44
+ fastify.log.warn(
45
+ `[API] Admin ${req.user._id} is dropping collections: ${req.body.collections.join(", ")}`,
46
+ )
47
+ result = await collectionService.dropCollections(
48
+ fastify,
49
+ req.body.collections,
50
+ )
51
+ }
52
+
53
+ return {
54
+ success: true,
55
+ dropped: result.dropped,
56
+ failed: result.failed,
57
+ message: `Successfully dropped ${result.dropped.length} collection(s)`,
58
+ }
59
+ } catch (error: any) {
60
+ fastify.log.error(error.message)
61
+ return reply.internalServerError(error.message)
62
+ }
63
+ }
64
+
65
+ return {
66
+ handler,
67
+ onRequest: [fastify.authenticate],
68
+ schema: {
69
+ summary: "Drop collections",
70
+ description:
71
+ "Drop selected MongoDB collections (admin only, debug feature - USE WITH CAUTION)",
72
+ tags: ["debug/collections"],
73
+ body: BodySchema,
74
+ response: {
75
+ 200: {
76
+ type: "object",
77
+ properties: {
78
+ success: { type: "boolean" },
79
+ dropped: {
80
+ type: "array",
81
+ items: { type: "string" },
82
+ },
83
+ failed: {
84
+ type: "array",
85
+ items: { type: "string" },
86
+ },
87
+ message: { type: "string" },
88
+ },
89
+ },
90
+ },
91
+ },
92
+ }
93
+ }
@@ -25,6 +25,7 @@ declare module "fastify" {
25
25
  uos: Client
26
26
  http: HttpService
27
27
  taskManager: TaskManager
28
+ core: any
28
29
  }
29
30
 
30
31
  interface FastifyRequest {