multi-content-type-relation 0.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/_chunks/en-Bk9okOMP.js +32 -0
  2. package/dist/_chunks/en-Cj4T04Z2.mjs +32 -0
  3. package/dist/_chunks/fr-KHPiQOFP.mjs +32 -0
  4. package/dist/_chunks/fr-ZS3aTnjj.js +32 -0
  5. package/dist/_chunks/index-BHcolZ4N.mjs +333 -0
  6. package/dist/_chunks/index-CktBIBSM.js +332 -0
  7. package/dist/_chunks/index-CxWt3llJ.js +3566 -0
  8. package/dist/_chunks/index-D6nv39Fp.mjs +3564 -0
  9. package/dist/admin/index.js +3 -0
  10. package/dist/admin/index.mjs +4 -0
  11. package/dist/admin/src/components/Input/InputContentSuggestions.d.ts +13 -0
  12. package/dist/admin/src/components/Input/MainInput.d.ts +16 -0
  13. package/dist/admin/src/components/Input/PublicationState.d.ts +6 -0
  14. package/dist/admin/src/components/Input/TableItem.d.ts +12 -0
  15. package/dist/admin/src/components/Input/index.d.ts +2 -0
  16. package/dist/admin/src/components/PluginIcon/index.d.ts +2 -0
  17. package/dist/admin/src/components/SidePanel/SidePanel.d.ts +7 -0
  18. package/dist/admin/src/helpers/content.d.ts +5 -0
  19. package/dist/admin/src/helpers/storage.d.ts +15 -0
  20. package/dist/admin/src/hooks/useSearchedEntries.d.ts +6 -0
  21. package/dist/admin/src/hooks/useTranslate.d.ts +4 -0
  22. package/dist/admin/src/index.d.ts +9 -0
  23. package/dist/admin/src/interface.d.ts +33 -0
  24. package/dist/admin/src/pluginId.d.ts +2 -0
  25. package/dist/server/index.js +506 -25
  26. package/dist/server/index.mjs +509 -0
  27. package/dist/server/src/bootstrap.d.ts +5 -0
  28. package/dist/server/src/config/index.d.ts +13 -0
  29. package/dist/server/src/content-types/index.d.ts +35 -0
  30. package/dist/server/src/content-types/mctr-relation/index.d.ts +33 -0
  31. package/dist/server/src/content-types/mctr-relation/schema.d.ts +31 -0
  32. package/dist/server/src/controllers/controller.d.ts +27 -0
  33. package/dist/server/src/controllers/index.d.ts +28 -0
  34. package/dist/server/src/destroy.d.ts +5 -0
  35. package/dist/server/src/helpers/index.d.ts +2 -0
  36. package/dist/server/src/index.d.ts +113 -0
  37. package/dist/server/src/interface.d.ts +42 -0
  38. package/dist/server/src/middlewares/index.d.ts +4 -0
  39. package/dist/server/src/middlewares/middleware.d.ts +2 -0
  40. package/dist/server/src/policies/index.d.ts +2 -0
  41. package/dist/server/src/register.d.ts +5 -0
  42. package/dist/server/src/routes/index.d.ts +18 -0
  43. package/dist/server/src/services/index.d.ts +8 -0
  44. package/dist/server/src/services/service.d.ts +7 -0
  45. package/dist/server/src/utils.d.ts +3 -0
  46. package/package.json +55 -27
  47. package/TODO.md +0 -4
  48. package/admin/src/components/Input/InputContentSuggestions.tsx +0 -162
  49. package/admin/src/components/Input/MainInput.tsx +0 -135
  50. package/admin/src/components/Input/PublicationState.tsx +0 -28
  51. package/admin/src/components/Input/TableItem.tsx +0 -109
  52. package/admin/src/components/Input/index.tsx +0 -27
  53. package/admin/src/components/PluginIcon/index.tsx +0 -12
  54. package/admin/src/helpers/content.ts +0 -60
  55. package/admin/src/helpers/storage.ts +0 -32
  56. package/admin/src/hooks/useSearchedEntries.ts +0 -41
  57. package/admin/src/index.tsx +0 -140
  58. package/admin/src/interface.ts +0 -37
  59. package/admin/src/pluginId.ts +0 -5
  60. package/admin/src/translations/en.json +0 -1
  61. package/admin/src/translations/fr.json +0 -1
  62. package/admin/src/utils/getTrad.ts +0 -5
  63. package/dist/server/bootstrap.js +0 -5
  64. package/dist/server/config/index.js +0 -27
  65. package/dist/server/content-types/index.js +0 -3
  66. package/dist/server/controllers/controller.js +0 -92
  67. package/dist/server/controllers/index.js +0 -9
  68. package/dist/server/destroy.js +0 -5
  69. package/dist/server/interface.js +0 -2
  70. package/dist/server/middlewares/index.js +0 -9
  71. package/dist/server/middlewares/middleware.js +0 -163
  72. package/dist/server/policies/index.js +0 -3
  73. package/dist/server/register.js +0 -15
  74. package/dist/server/routes/index.js +0 -29
  75. package/dist/server/services/index.js +0 -9
  76. package/dist/server/services/service.js +0 -8
  77. package/dist/server/utils.js +0 -15
  78. package/dist/tsconfig.server.tsbuildinfo +0 -1
  79. package/server/bootstrap.ts +0 -5
  80. package/server/config/index.ts +0 -28
  81. package/server/content-types/index.ts +0 -1
  82. package/server/controllers/controller.ts +0 -107
  83. package/server/controllers/index.ts +0 -5
  84. package/server/destroy.ts +0 -5
  85. package/server/index.ts +0 -23
  86. package/server/interface.ts +0 -50
  87. package/server/middlewares/index.ts +0 -5
  88. package/server/middlewares/middleware.ts +0 -197
  89. package/server/policies/index.ts +0 -1
  90. package/server/register.ts +0 -14
  91. package/server/routes/index.ts +0 -27
  92. package/server/services/index.ts +0 -5
  93. package/server/services/service.ts +0 -11
  94. package/server/utils.ts +0 -14
  95. package/strapi-admin.js +0 -3
  96. package/strapi-server.js +0 -3
  97. package/tsconfig.json +0 -20
  98. package/tsconfig.server.json +0 -25
@@ -1,107 +0,0 @@
1
- import { Strapi } from "@strapi/strapi"
2
- import type { Common } from "@strapi/strapi"
3
- import { FormattedStrapiEntry } from "../interface"
4
-
5
- export default ({ strapi }: { strapi: Strapi }) => ({
6
- getMatchingContent(ctx) {
7
- const contentTypes = strapi.contentTypes
8
- const body = ctx.request.body
9
-
10
- const requestedContentTypes = body.contentTypes as string[]
11
- const keyword = body.keyword as string
12
- const locale = body.locale as string
13
-
14
- const mapping = requestedContentTypes.reduce((accumulator, contentType) => {
15
- Object.keys(contentTypes).forEach((model) => {
16
- const strapiContentType = contentTypes[model]
17
- if (strapiContentType.info.singularName === contentType || strapiContentType.info.pluralName === contentType) {
18
- accumulator[contentType] = {
19
- uid: model,
20
- displayName: contentTypes[model].info.displayName,
21
- searchableField: strapi
22
- .plugin("multi-content-type-relation")
23
- .service("service")
24
- .getFirstStringFieldInContentType(contentTypes[model])
25
- }
26
- }
27
- })
28
-
29
- return accumulator
30
- }, {} as Record<string, { uid: string; displayName: string; searchableField: string }>)
31
-
32
- const promises = Object.keys(mapping).map((contentType) => {
33
- const uid = mapping[contentType].uid
34
-
35
- return strapi
36
- .entityService!.findMany(uid as Common.UID.ContentType, {
37
- filters: {
38
- [mapping[contentType].searchableField]: {
39
- $containsi: keyword
40
- }
41
- },
42
- locale
43
- })
44
- .then((results) => {
45
- let contents = Array.isArray(results) ? results : typeof results === "object" && results ? [results] : []
46
-
47
- const contentTypeDefinition = strapi.contentType(uid as Common.UID.ContentType)
48
- if (contentTypeDefinition?.options?.draftAndPublish) {
49
- contents = contents.filter((content) => content.publishedAt !== null)
50
- }
51
-
52
- return {
53
- uid,
54
- displayName: mapping[contentType].displayName,
55
- searchableField: mapping[contentType].searchableField,
56
- results: contents
57
- }
58
- })
59
- })
60
-
61
- return Promise.all(promises)
62
- },
63
- validateRelations: async function (ctx) {
64
- const contentTypes = strapi.contentTypes
65
- const body = ctx.request.body
66
-
67
- const entries = body.entries as FormattedStrapiEntry[]
68
-
69
- const promises = entries.map((entry) => {
70
- return strapi
71
- .entityService!.findOne(entry.uid as Common.UID.ContentType, entry.id, { populate: "deep" })
72
- .then((result) => {
73
- return {
74
- uid: entry.uid,
75
- result
76
- }
77
- })
78
- })
79
-
80
- const responses = await Promise.all(promises)
81
-
82
- return responses
83
- .map((response) => {
84
- return {
85
- displayName: contentTypes[response.uid].info.displayName,
86
- uid: response.uid,
87
- searchableField: strapi
88
- .plugin("multi-content-type-relation")
89
- .service("service")
90
- .getFirstStringFieldInContentType(contentTypes[response.uid]),
91
- item: response.result
92
- }
93
- })
94
- .filter((entry) => entry.item)
95
- },
96
- listContentTypes: async function (ctx) {
97
- const contentTypes: Record<string, unknown>[] = []
98
-
99
- for (const contentType of Object.values(strapi.contentTypes) as { kind: string; plugin: boolean }[]) {
100
- if ((contentType.kind === "collectionType" || contentType.kind === "singleType") && !contentType.plugin) {
101
- contentTypes.push(contentType as Record<string, unknown>)
102
- }
103
- }
104
-
105
- return contentTypes
106
- }
107
- })
@@ -1,5 +0,0 @@
1
- import controller from "./controller"
2
-
3
- export default {
4
- controller
5
- }
package/server/destroy.ts DELETED
@@ -1,5 +0,0 @@
1
- import { Strapi } from '@strapi/strapi';
2
-
3
- export default ({ strapi }: { strapi: Strapi }) => {
4
- // destroy phase
5
- };
package/server/index.ts DELETED
@@ -1,23 +0,0 @@
1
- import register from './register';
2
- import bootstrap from './bootstrap';
3
- import destroy from './destroy';
4
- import config from './config';
5
- import contentTypes from './content-types';
6
- import controllers from './controllers';
7
- import routes from './routes';
8
- import middlewares from './middlewares';
9
- import policies from './policies';
10
- import services from './services';
11
-
12
- export default {
13
- register,
14
- bootstrap,
15
- destroy,
16
- config,
17
- controllers,
18
- routes,
19
- services,
20
- contentTypes,
21
- policies,
22
- middlewares,
23
- };
@@ -1,50 +0,0 @@
1
- export type StrapiContentTypeDefinition = {
2
- collectionName: string
3
- info: {
4
- name: string
5
- description: string
6
- singularName: string
7
- pluralName: string
8
- displayName: string
9
- }
10
- attributes: Record<string, unknown>
11
- }
12
-
13
- export type SelectedEntry = {
14
- displayName: string
15
- searchableField: string
16
- uid: string
17
- item: {
18
- id: string
19
- [key: string]: any
20
- }
21
- }
22
-
23
- export type FormattedStrapiEntry = {
24
- uid: string
25
- id: string
26
- }
27
-
28
- export type Configuration = {
29
- recursive: {
30
- enabled: boolean
31
- maxDepth: number
32
- }
33
- debug: boolean
34
- }
35
-
36
- export type AnyEntity = {
37
- id: number | string
38
- attributes: {
39
- [key: string]: any
40
- }
41
- }
42
-
43
- export type StrapiResponse = {
44
- data: AnyEntity | AnyEntity[]
45
- }
46
-
47
- export type Context = {
48
- configuration: Configuration
49
- publicationState: "live" | "preview"
50
- }
@@ -1,5 +0,0 @@
1
- import middleware from "./middleware"
2
-
3
- export default {
4
- middleware
5
- }
@@ -1,197 +0,0 @@
1
- import type { Common } from "@strapi/strapi"
2
- import { getPluginConfiguration, log } from "../utils"
3
- import type { Context, StrapiResponse, AnyEntity } from "../interface"
4
-
5
- export default async (ctx, next) => {
6
- await next()
7
-
8
- if (!ctx?.request?.url?.startsWith("/api")) return
9
- if (ctx.request.method !== "GET") return
10
- if (!ctx.body) return
11
-
12
- const configuration = getPluginConfiguration()
13
-
14
- const handler = ctx.state.route.handler
15
- const contentTypes = Object.keys(strapi.contentTypes)
16
-
17
- log(`URL: ${ctx.request.url} (${ctx.request.method})`)
18
- log(`Strapi Route: ${JSON.stringify(ctx.state.route, null, 2)}`)
19
-
20
- const validHandler = contentTypes
21
- .filter((contentType) => contentType.startsWith("api::"))
22
- .some(
23
- (contentType) =>
24
- handler.includes(`${contentType}.findOne`) ||
25
- handler.includes(`${contentType}.findMany`) ||
26
- handler.includes(`${contentType}.find`)
27
- )
28
-
29
- log(`Is valid handler: ${validHandler}`)
30
-
31
- // Allow only findOne/findMany for native contentypes that have api::
32
- if (!validHandler) return
33
-
34
- const context = {
35
- configuration,
36
- publicationState: ctx.request.query?.["publicationState"] ?? "live"
37
- }
38
-
39
- log(" ----- ")
40
- log(`Context Body: ${JSON.stringify(ctx.body, null, 2)}`)
41
- if (ctx.body.error || !ctx.body?.data.attributes) return
42
-
43
- const hydratedData = await augmentMRCT(ctx.body, 1, context)
44
-
45
- ctx.body.data = hydratedData
46
- }
47
-
48
- const augmentMRCT = async (
49
- strapiResponse: StrapiResponse,
50
- currentDepth: number,
51
- context: Context
52
- ): Promise<AnyEntity | AnyEntity[]> => {
53
- if (Array.isArray(strapiResponse.data)) {
54
- const promises = strapiResponse.data.map((item) => hydrateMRCT(item, currentDepth, context))
55
-
56
- return await Promise.all(promises)
57
- } else {
58
- return await hydrateMRCT(strapiResponse.data, currentDepth, context)
59
- }
60
- }
61
-
62
- const hydrateMRCT = async (content: AnyEntity, currentDepth: number, context: Context) => {
63
- const eligibleProperties: Set<string> = new Set()
64
- const contentsToFetch: Set<string> = new Set()
65
-
66
- const { configuration } = context
67
-
68
- const flattenedProperties = flattenObj(content.attributes, null)
69
-
70
- for (const [key, value] of Object.entries(flattenedProperties)) {
71
- if (typeof value !== "string" || !value.includes("MRCT")) continue
72
-
73
- try {
74
- const field = JSON.parse(value)
75
-
76
- if (!Array.isArray(field)) continue
77
-
78
- for (const item of field) {
79
- if (Object.keys(item).length !== 3 || (!item.uid && typeof item.uid !== "string") || !item.id) continue
80
-
81
- const compositeID = `${item.uid}####${item.id}`
82
-
83
- eligibleProperties.add(key)
84
-
85
- if (contentsToFetch.has(compositeID)) continue
86
- else contentsToFetch.add(compositeID)
87
- }
88
- } catch (e) {
89
- continue
90
- }
91
- }
92
-
93
- if (!contentsToFetch.size) return content
94
-
95
- log(`Depth: ${currentDepth}, Hydrating MCTR for ID ${content.id}`)
96
-
97
- const promises: Promise<any>[] = []
98
- for (const item of Array.from(contentsToFetch)) {
99
- const [uid, id] = item.split("####")
100
- const promise = strapi.entityService
101
- .findOne(uid as Common.UID.ContentType, id, { populate: "deep" })
102
- .then(async (response) => {
103
- if (!response) return { uid, response }
104
-
105
- if (configuration.recursive.enabled && currentDepth < configuration.recursive.maxDepth) {
106
- // Entity service serve the content flattened, so we need to rebuild the API format for the hydrate recursion
107
- const hydratedResponse = await hydrateMRCT(
108
- {
109
- id: response.id,
110
- attributes: response
111
- },
112
- currentDepth + 1,
113
- context
114
- )
115
-
116
- return {
117
- uid,
118
- response: {
119
- id: response.id,
120
- attributes: hydratedResponse.attributes
121
- }
122
- }
123
- } else {
124
- return {
125
- uid,
126
- response: {
127
- id: response.id,
128
- attributes: response
129
- }
130
- }
131
- }
132
- })
133
-
134
- promises.push(promise)
135
- }
136
-
137
- const linkedEntries: any[] = await Promise.all(promises)
138
-
139
- const filteredLinkedEntries: { uid: string; response: AnyEntity }[] = linkedEntries
140
- .filter((linkedEntry) => Boolean(linkedEntry.response))
141
- .filter((linkedEntry) => {
142
- const contentTypeConfiguration = strapi.contentTypes[linkedEntry.uid]
143
-
144
- if (!contentTypeConfiguration) return true
145
- if (!contentTypeConfiguration.options?.draftAndPublish) return true
146
- if (context.publicationState === "preview") return true
147
-
148
- return typeof linkedEntry.response?.attributes.publishedAt === "string"
149
- })
150
-
151
- for (const key of Array.from(eligibleProperties)) {
152
- const hydratedArray: AnyEntity[] = []
153
-
154
- const unhydratedField = JSON.parse(flattenedProperties[key]) as { uid: string; id: string }[]
155
-
156
- for (const item of unhydratedField) {
157
- const matchingContent = filteredLinkedEntries.find(
158
- (linkedEntry) => item.uid === linkedEntry.uid && item.id === linkedEntry.response.id
159
- )
160
-
161
- if (matchingContent) {
162
- hydratedArray.push(matchingContent.response)
163
- }
164
- }
165
-
166
- flattenedProperties[key] = hydratedArray
167
- }
168
-
169
- const newContent = unflatten(flattenedProperties)
170
- return {
171
- ...content,
172
- attributes: newContent
173
- }
174
- }
175
-
176
- const flattenObj = (obj: any, parent: any, res: Record<string, any> = {}) => {
177
- for (let key in obj) {
178
- let propName = parent ? parent + "." + key : key
179
- if (typeof obj[key] == "object") {
180
- flattenObj(obj[key], propName, res)
181
- } else {
182
- res[propName] = obj[key]
183
- }
184
- }
185
- return res
186
- }
187
-
188
- const unflatten = (data: any) => {
189
- var result = {}
190
- for (var i in data) {
191
- var keys = i.split(".")
192
- keys.reduce(function (r: any, e, j) {
193
- return r[e] || (r[e] = isNaN(Number(keys[j + 1])) ? (keys.length - 1 == j ? data[i] : {}) : [])
194
- }, result)
195
- }
196
- return result
197
- }
@@ -1 +0,0 @@
1
- export default {};
@@ -1,14 +0,0 @@
1
- import { Strapi } from "@strapi/strapi"
2
-
3
- import middlewares from "./middlewares"
4
-
5
- export default ({ strapi }: { strapi: Strapi }) => {
6
- // register phase
7
- strapi.customFields.register({
8
- name: "multi-content-type-relation",
9
- plugin: "multi-content-type-relation",
10
- type: "richtext"
11
- })
12
-
13
- strapi.server.use(middlewares.middleware)
14
- }
@@ -1,27 +0,0 @@
1
- export default [
2
- {
3
- method: "GET",
4
- path: "/list-content-types",
5
- handler: "controller.listContentTypes",
6
- config: {
7
- policies: [],
8
- auth: false
9
- }
10
- },
11
- {
12
- method: "POST",
13
- path: "/get-content",
14
- handler: "controller.getMatchingContent",
15
- config: {
16
- policies: []
17
- }
18
- },
19
- {
20
- method: "POST",
21
- path: "/validate-relations",
22
- handler: "controller.validateRelations",
23
- config: {
24
- policies: []
25
- }
26
- }
27
- ]
@@ -1,5 +0,0 @@
1
- import service from "./service"
2
-
3
- export default {
4
- service
5
- }
@@ -1,11 +0,0 @@
1
- import { Strapi } from "@strapi/strapi"
2
-
3
- export default ({ strapi }: { strapi: Strapi }) => ({
4
- getFirstStringFieldInContentType(contentType) {
5
- const result = Object.keys(contentType.attributes).find(
6
- (attribute) => contentType.attributes[attribute].type === "string"
7
- )
8
-
9
- return result
10
- }
11
- })
package/server/utils.ts DELETED
@@ -1,14 +0,0 @@
1
- import { Configuration } from "./interface"
2
-
3
- export const getPluginConfiguration = (): Configuration => {
4
- const pluginConfiguration = strapi.config.get("plugin.multi-content-type-relation") as Configuration
5
-
6
- return pluginConfiguration
7
- }
8
- export const log = (message: string) => {
9
- const { debug } = getPluginConfiguration()
10
-
11
- if (debug) {
12
- console.log(`[MCTR DEBUG] ${message}`)
13
- }
14
- }
package/strapi-admin.js DELETED
@@ -1,3 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = require('./admin/src').default;
package/strapi-server.js DELETED
@@ -1,3 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = require('./dist/server');
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "extends": "@strapi/typescript-utils/tsconfigs/admin",
3
-
4
- "compilerOptions": {
5
- "target": "ESNext",
6
- "strict": true
7
- },
8
-
9
- "include": ["admin", "custom.d.ts"],
10
-
11
- "exclude": [
12
- "node_modules/",
13
- "dist/",
14
-
15
- // Do not include server files in the server compilation
16
- "server/",
17
- // Do not include test files
18
- "**/*.test.ts"
19
- ]
20
- }
@@ -1,25 +0,0 @@
1
- {
2
- "extends": "@strapi/typescript-utils/tsconfigs/server",
3
-
4
- "compilerOptions": {
5
- "outDir": "dist",
6
- "rootDir": "."
7
- },
8
-
9
- "include": [
10
- // Include the root directory
11
- "server",
12
- // Force the JSON files in the src folder to be included
13
- "server/**/*.json"
14
- ],
15
-
16
- "exclude": [
17
- "node_modules/",
18
- "dist/",
19
-
20
- // Do not include admin files in the server compilation
21
- "admin/",
22
- // Do not include test files
23
- "**/*.test.ts"
24
- ]
25
- }