strapi-plugin-i-relate-to-this 0.0.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.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # strapi-plugin-i-relate-to-this
2
+
3
+ List unidirectional relations (in components) to the current content entity.
4
+
5
+ ## What does this do
6
+
7
+ Uni-directional relations are only visible on one side of the relation. This plugin will create a list of content entities that have a uni relation to the current entity.
8
+
9
+ ## Why
10
+
11
+ Strapi 5 allows you to relate (from source) to draft content (the target). When you publish that (target) related entity, it is not directly synced to the published version of the (source) entity that relates to it. It (the target) will remain unpublished until you publish the (source) entity.
12
+
13
+ Imagine having hunderds of relations to some campaign which ends, you unpublish it, and next month you publish it again. Good luck finding all uni-relations inside of components to it. This plugin will help you!
14
+
15
+ This plugin lists all entities that have uni-relations (deeply nested in DZs/components) to the current entity you are editing. It will warn you (⚠) when the (source) entity is published but the related (target) entity does not contain the relation in their published version.
16
+
17
+ ## Help wanted
18
+
19
+ Currently, I have been focussing on getting the data from the db. The UI isn't pretty. Feel free to make a PR to make it pretty, or to improve the lookup logic. Thanks <3
20
+
21
+ ## TODO
22
+
23
+ - Create a pretty UI
24
+ - Maybe I should not auto-load the list, but have a button that requests it. (there's a lot of db queries happening to get this data)
25
+
26
+ ## Related issues
27
+
28
+ https://github.com/strapi/strapi/issues/23460
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const react = require("react");
4
+ const reactIntl = require("react-intl");
5
+ const reactRouterDom = require("react-router-dom");
6
+ const designSystem = require("@strapi/design-system");
7
+ const admin = require("@strapi/strapi/admin");
8
+ const List = () => {
9
+ const { formatMessage } = reactIntl.useIntl();
10
+ const { id, slug } = reactRouterDom.useParams();
11
+ const [params] = reactRouterDom.useSearchParams();
12
+ const { toggleNotification } = admin.useNotification();
13
+ const [items, setItems] = react.useState([]);
14
+ const status = params.get("status") || "draft";
15
+ const getter = react.useCallback(async () => {
16
+ const { get } = admin.getFetchClient();
17
+ let response;
18
+ const endpoint = `/i-relate-to-this/list/${slug}/${id}/${status}`;
19
+ try {
20
+ response = await get(endpoint, { id });
21
+ } catch (error) {
22
+ return toggleNotification({
23
+ type: "warning",
24
+ message: formatMessage({
25
+ id: "Failed to load list",
26
+ defaultMessage: error
27
+ })
28
+ });
29
+ }
30
+ setItems(response.data.items);
31
+ return null;
32
+ }, [formatMessage, id, toggleNotification, slug, status, setItems]);
33
+ react.useEffect(() => {
34
+ if (id && id !== "create") {
35
+ getter();
36
+ }
37
+ }, [id, getter]);
38
+ return items.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
39
+ "Links:",
40
+ items.map(({
41
+ documentId,
42
+ title,
43
+ uid,
44
+ contentTypeDisplayName,
45
+ isPublished
46
+ }) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsxs(
47
+ designSystem.Link,
48
+ {
49
+ href: `/admin/content-manager/collection-types/${uid}/${documentId}`,
50
+ children: [
51
+ !isPublished && "⚠",
52
+ " ",
53
+ contentTypeDisplayName,
54
+ ": ",
55
+ title || documentId
56
+ ]
57
+ }
58
+ ) }, documentId))
59
+ ] });
60
+ };
61
+ const index = {
62
+ bootstrap(app) {
63
+ app.getPlugin("content-manager").injectComponent("editView", "right-links", {
64
+ name: "it-relates-to-me-list",
65
+ Component: List
66
+ });
67
+ },
68
+ register() {
69
+ }
70
+ };
71
+ module.exports = index;
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../admin/src/components/list.jsx","../../admin/src/index.js"],"sourcesContent":["import React, { useEffect, useCallback, useState } from 'react';\nimport { useIntl } from 'react-intl';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { Link } from '@strapi/design-system';\nimport {\n getFetchClient,\n useNotification,\n} from '@strapi/strapi/admin';\n\nconst useThisCode = true;\n\nconst List = () => {\n const { formatMessage } = useIntl();\n const { id, slug } = useParams();\n const [params] = useSearchParams();\n const { toggleNotification } = useNotification();\n const [items, setItems] = useState([]);\n const status = params.get('status') || 'draft';\n\n const getter = useCallback(async () => {\n const { get } = getFetchClient();\n let response;\n const endpoint = `/i-relate-to-this/list/${slug}/${id}/${status}`;\n try {\n response = await get(endpoint, { id });\n } catch (error) {\n return toggleNotification({\n type: 'warning',\n message: formatMessage({\n id: 'Failed to load list',\n defaultMessage: error,\n }),\n });\n }\n setItems(response.data.items);\n return null;\n }, [formatMessage, id, toggleNotification, slug, status, setItems]);\n\n useEffect(() => {\n // single types don't have an id\n // on create there are never relations\n if (useThisCode && id && id !== 'create') {\n getter();\n }\n }, [id, getter]);\n\n if (!useThisCode) {\n return null;\n }\n\n return items.length > 0 && (\n <div>\n Links:\n {items.map(({\n documentId,\n title,\n uid,\n contentTypeDisplayName,\n isPublished,\n }) => (\n <div key={documentId}>\n <Link\n href={`/admin/content-manager/collection-types/${uid}/${documentId}`}\n >\n {!isPublished && '⚠'} {contentTypeDisplayName}: {title || documentId}\n </Link>\n </div>\n ))}\n </div>\n );\n};\n\nexport default List;\n","import List from './components/list';\n\nexport default {\n bootstrap(app) {\n // execute some bootstrap code\n app.getPlugin('content-manager').injectComponent('editView', 'right-links', {\n name: 'it-relates-to-me-list',\n Component: List,\n });\n },\n register() {}\n};\n"],"names":["useIntl","useParams","useSearchParams","useNotification","useState","useCallback","getFetchClient","useEffect","jsxs","Link"],"mappings":";;;;;;;AAWA,MAAM,OAAO,MAAM;AACjB,QAAM,EAAE,cAAA,IAAkBA,kBAAA;AAC1B,QAAM,EAAE,IAAI,KAAA,IAASC,yBAAA;AACrB,QAAM,CAAC,MAAM,IAAIC,+BAAA;AACjB,QAAM,EAAE,mBAAA,IAAuBC,sBAAA;AAC/B,QAAM,CAAC,OAAO,QAAQ,IAAIC,MAAAA,SAAS,CAAA,CAAE;AACrC,QAAM,SAAS,OAAO,IAAI,QAAQ,KAAK;AAEvC,QAAM,SAASC,MAAAA,YAAY,YAAY;AACrC,UAAM,EAAE,IAAA,IAAQC,qBAAA;AAChB,QAAI;AACJ,UAAM,WAAW,0BAA0B,IAAI,IAAI,EAAE,IAAI,MAAM;AAC/D,QAAI;AACF,iBAAW,MAAM,IAAI,UAAU,EAAE,IAAI;AAAA,IACvC,SAAS,OAAO;AACd,aAAO,mBAAmB;AAAA,QACxB,MAAM;AAAA,QACN,SAAS,cAAc;AAAA,UACrB,IAAI;AAAA,UACJ,gBAAgB;AAAA,QAAA,CACjB;AAAA,MAAA,CACF;AAAA,IACH;AACA,aAAS,SAAS,KAAK,KAAK;AAC5B,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,IAAI,oBAAoB,MAAM,QAAQ,QAAQ,CAAC;AAElEC,QAAAA,UAAU,MAAM;AAGd,QAAmB,MAAM,OAAO,UAAU;AACxC,aAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,IAAI,MAAM,CAAC;AAMf,SAAO,MAAM,SAAS,KACpBC,2BAAAA,KAAC,OAAA,EAAI,UAAA;AAAA,IAAA;AAAA,IAEF,MAAM,IAAI,CAAC;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,qCAEC,OAAA,EACC,UAAAA,2BAAAA;AAAAA,MAACC,aAAAA;AAAAA,MAAA;AAAA,QACC,MAAM,2CAA2C,GAAG,IAAI,UAAU;AAAA,QAEjE,UAAA;AAAA,UAAA,CAAC,eAAe;AAAA,UAAI;AAAA,UAAE;AAAA,UAAuB;AAAA,UAAG,SAAS;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,EAC5D,GALQ,UAMV,CACD;AAAA,EAAA,GACH;AAEJ;ACpEA,MAAA,QAAe;AAAA,EACb,UAAU,KAAK;AAEb,QAAI,UAAU,iBAAiB,EAAE,gBAAgB,YAAY,eAAe;AAAA,MAC1E,MAAM;AAAA,MACN,WAAW;AAAA,IACjB,CAAK;AAAA,EACH;AAAA,EACA,WAAW;AAAA,EAAC;AACd;;"}
@@ -0,0 +1,73 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useCallback, useEffect } from "react";
3
+ import { useIntl } from "react-intl";
4
+ import { useParams, useSearchParams } from "react-router-dom";
5
+ import { Link } from "@strapi/design-system";
6
+ import { useNotification, getFetchClient } from "@strapi/strapi/admin";
7
+ const List = () => {
8
+ const { formatMessage } = useIntl();
9
+ const { id, slug } = useParams();
10
+ const [params] = useSearchParams();
11
+ const { toggleNotification } = useNotification();
12
+ const [items, setItems] = useState([]);
13
+ const status = params.get("status") || "draft";
14
+ const getter = useCallback(async () => {
15
+ const { get } = getFetchClient();
16
+ let response;
17
+ const endpoint = `/i-relate-to-this/list/${slug}/${id}/${status}`;
18
+ try {
19
+ response = await get(endpoint, { id });
20
+ } catch (error) {
21
+ return toggleNotification({
22
+ type: "warning",
23
+ message: formatMessage({
24
+ id: "Failed to load list",
25
+ defaultMessage: error
26
+ })
27
+ });
28
+ }
29
+ setItems(response.data.items);
30
+ return null;
31
+ }, [formatMessage, id, toggleNotification, slug, status, setItems]);
32
+ useEffect(() => {
33
+ if (id && id !== "create") {
34
+ getter();
35
+ }
36
+ }, [id, getter]);
37
+ return items.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
38
+ "Links:",
39
+ items.map(({
40
+ documentId,
41
+ title,
42
+ uid,
43
+ contentTypeDisplayName,
44
+ isPublished
45
+ }) => /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
46
+ Link,
47
+ {
48
+ href: `/admin/content-manager/collection-types/${uid}/${documentId}`,
49
+ children: [
50
+ !isPublished && "⚠",
51
+ " ",
52
+ contentTypeDisplayName,
53
+ ": ",
54
+ title || documentId
55
+ ]
56
+ }
57
+ ) }, documentId))
58
+ ] });
59
+ };
60
+ const index = {
61
+ bootstrap(app) {
62
+ app.getPlugin("content-manager").injectComponent("editView", "right-links", {
63
+ name: "it-relates-to-me-list",
64
+ Component: List
65
+ });
66
+ },
67
+ register() {
68
+ }
69
+ };
70
+ export {
71
+ index as default
72
+ };
73
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../../admin/src/components/list.jsx","../../admin/src/index.js"],"sourcesContent":["import React, { useEffect, useCallback, useState } from 'react';\nimport { useIntl } from 'react-intl';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { Link } from '@strapi/design-system';\nimport {\n getFetchClient,\n useNotification,\n} from '@strapi/strapi/admin';\n\nconst useThisCode = true;\n\nconst List = () => {\n const { formatMessage } = useIntl();\n const { id, slug } = useParams();\n const [params] = useSearchParams();\n const { toggleNotification } = useNotification();\n const [items, setItems] = useState([]);\n const status = params.get('status') || 'draft';\n\n const getter = useCallback(async () => {\n const { get } = getFetchClient();\n let response;\n const endpoint = `/i-relate-to-this/list/${slug}/${id}/${status}`;\n try {\n response = await get(endpoint, { id });\n } catch (error) {\n return toggleNotification({\n type: 'warning',\n message: formatMessage({\n id: 'Failed to load list',\n defaultMessage: error,\n }),\n });\n }\n setItems(response.data.items);\n return null;\n }, [formatMessage, id, toggleNotification, slug, status, setItems]);\n\n useEffect(() => {\n // single types don't have an id\n // on create there are never relations\n if (useThisCode && id && id !== 'create') {\n getter();\n }\n }, [id, getter]);\n\n if (!useThisCode) {\n return null;\n }\n\n return items.length > 0 && (\n <div>\n Links:\n {items.map(({\n documentId,\n title,\n uid,\n contentTypeDisplayName,\n isPublished,\n }) => (\n <div key={documentId}>\n <Link\n href={`/admin/content-manager/collection-types/${uid}/${documentId}`}\n >\n {!isPublished && '⚠'} {contentTypeDisplayName}: {title || documentId}\n </Link>\n </div>\n ))}\n </div>\n );\n};\n\nexport default List;\n","import List from './components/list';\n\nexport default {\n bootstrap(app) {\n // execute some bootstrap code\n app.getPlugin('content-manager').injectComponent('editView', 'right-links', {\n name: 'it-relates-to-me-list',\n Component: List,\n });\n },\n register() {}\n};\n"],"names":[],"mappings":";;;;;;AAWA,MAAM,OAAO,MAAM;AACjB,QAAM,EAAE,cAAA,IAAkB,QAAA;AAC1B,QAAM,EAAE,IAAI,KAAA,IAAS,UAAA;AACrB,QAAM,CAAC,MAAM,IAAI,gBAAA;AACjB,QAAM,EAAE,mBAAA,IAAuB,gBAAA;AAC/B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,CAAA,CAAE;AACrC,QAAM,SAAS,OAAO,IAAI,QAAQ,KAAK;AAEvC,QAAM,SAAS,YAAY,YAAY;AACrC,UAAM,EAAE,IAAA,IAAQ,eAAA;AAChB,QAAI;AACJ,UAAM,WAAW,0BAA0B,IAAI,IAAI,EAAE,IAAI,MAAM;AAC/D,QAAI;AACF,iBAAW,MAAM,IAAI,UAAU,EAAE,IAAI;AAAA,IACvC,SAAS,OAAO;AACd,aAAO,mBAAmB;AAAA,QACxB,MAAM;AAAA,QACN,SAAS,cAAc;AAAA,UACrB,IAAI;AAAA,UACJ,gBAAgB;AAAA,QAAA,CACjB;AAAA,MAAA,CACF;AAAA,IACH;AACA,aAAS,SAAS,KAAK,KAAK;AAC5B,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,IAAI,oBAAoB,MAAM,QAAQ,QAAQ,CAAC;AAElE,YAAU,MAAM;AAGd,QAAmB,MAAM,OAAO,UAAU;AACxC,aAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,IAAI,MAAM,CAAC;AAMf,SAAO,MAAM,SAAS,KACpB,qBAAC,OAAA,EAAI,UAAA;AAAA,IAAA;AAAA,IAEF,MAAM,IAAI,CAAC;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,0BAEC,OAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM,2CAA2C,GAAG,IAAI,UAAU;AAAA,QAEjE,UAAA;AAAA,UAAA,CAAC,eAAe;AAAA,UAAI;AAAA,UAAE;AAAA,UAAuB;AAAA,UAAG,SAAS;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,EAC5D,GALQ,UAMV,CACD;AAAA,EAAA,GACH;AAEJ;ACpEA,MAAA,QAAe;AAAA,EACb,UAAU,KAAK;AAEb,QAAI,UAAU,iBAAiB,EAAE,gBAAgB,YAAY,eAAe;AAAA,MAC1E,MAAM;AAAA,MACN,WAAW;AAAA,IACjB,CAAK;AAAA,EACH;AAAA,EACA,WAAW;AAAA,EAAC;AACd;"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ const snakeCase = require("lodash.snakecase");
3
+ const mergeWith = require("lodash.mergewith");
4
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
5
+ const snakeCase__default = /* @__PURE__ */ _interopDefault(snakeCase);
6
+ const mergeWith__default = /* @__PURE__ */ _interopDefault(mergeWith);
7
+ const bootstrap = ({ strapi: strapi2 }) => {
8
+ };
9
+ const destroy = ({ strapi: strapi2 }) => {
10
+ };
11
+ const register = ({ strapi: strapi2 }) => {
12
+ };
13
+ const config = {
14
+ default: {},
15
+ validator() {
16
+ }
17
+ };
18
+ const contentTypes = {};
19
+ const controller = ({ strapi: strapi2 }) => ({
20
+ async getunirelations(ctx) {
21
+ const { id, contentType, status } = ctx.request.params;
22
+ const response = await strapi2.service("plugin::i-relate-to-this.service").getunirelations(contentType, id, status);
23
+ if (!response) {
24
+ ctx.body = { success: false };
25
+ return;
26
+ }
27
+ ctx.body = {
28
+ success: true,
29
+ items: response
30
+ };
31
+ }
32
+ });
33
+ const controllers = {
34
+ controller
35
+ };
36
+ const middlewares = {};
37
+ const policies = {};
38
+ const adminRoutes = [
39
+ {
40
+ method: "GET",
41
+ path: "/list/:contentType/:id/:status",
42
+ handler: "controller.getunirelations"
43
+ }
44
+ ];
45
+ const routes = {
46
+ admin: {
47
+ type: "admin",
48
+ routes: adminRoutes
49
+ }
50
+ };
51
+ function customizer(objValue, srcValue) {
52
+ if (Array.isArray(objValue)) {
53
+ return objValue.includes(srcValue[0]) ? objValue : objValue.concat(srcValue[0]);
54
+ }
55
+ return void 0;
56
+ }
57
+ const search = async (id, status, parent) => {
58
+ const modelsWithRelationsToMe = Object.values({ ...strapi.contentTypes, ...strapi.components }).reduce((total, model) => {
59
+ const {
60
+ attributes
61
+ } = model;
62
+ const relations = Object.entries(attributes).reduce((acc, [key, {
63
+ type,
64
+ target,
65
+ inversedBy,
66
+ mappedBy,
67
+ component,
68
+ components
69
+ }]) => {
70
+ if (key === "localizations") {
71
+ return acc;
72
+ }
73
+ if (
74
+ // only uni relations
75
+ !(type === "relation" && target === parent.uid && !inversedBy && !mappedBy) && !(type === "component" && component === parent.uid) && !(type === "dynamiczone" && components?.includes(parent.uid))
76
+ ) {
77
+ return acc;
78
+ }
79
+ return acc.concat({
80
+ key,
81
+ type,
82
+ ...model
83
+ });
84
+ }, []);
85
+ return total.concat(relations);
86
+ }, []);
87
+ const lookups = modelsWithRelationsToMe.reduce(async (acc, {
88
+ key,
89
+ type,
90
+ ...model
91
+ }) => {
92
+ const {
93
+ collectionName,
94
+ modelName
95
+ } = model;
96
+ const relationIdColumn = type === "relation" ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(
97
+ snakeCase__default.default(modelName)
98
+ ) : "entity_id";
99
+ const parentIdColumn = type === "relation" ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(
100
+ snakeCase__default.default(parent.modelName)
101
+ ) : "cmp_id";
102
+ const tableName = type === "relation" ? `${snakeCase__default.default(`${collectionName} ${key} lnk`)}` : `${collectionName}_cmps`;
103
+ const relationsInTable = await strapi.db.getConnection(tableName).where({
104
+ [parentIdColumn]: id,
105
+ ...type === "relation" ? {} : {
106
+ component_type: parent.uid
107
+ }
108
+ });
109
+ if (relationsInTable.length === 0) {
110
+ return acc;
111
+ }
112
+ const itemsForThisCollection = relationsInTable.reduce(async (deepacc, item) => {
113
+ const prev = await deepacc;
114
+ if (model.uid.startsWith("api::") || model.uid.startsWith("plugin::")) {
115
+ return mergeWith__default.default(prev, {
116
+ [model.uid]: {
117
+ info: model.info,
118
+ items: [item[relationIdColumn]]
119
+ }
120
+ }, customizer);
121
+ }
122
+ const next = await search(item[relationIdColumn], status, model);
123
+ return mergeWith__default.default(prev, next, customizer);
124
+ }, {});
125
+ return mergeWith__default.default(await acc, await itemsForThisCollection, customizer);
126
+ }, {});
127
+ return lookups;
128
+ };
129
+ const service = ({ strapi: strapi2 }) => ({
130
+ async getunirelations(contentType, documentId, status = "draft") {
131
+ console.log("running service");
132
+ const ct = strapi2.contentType(contentType);
133
+ const entity = await strapi2.documents(contentType).findOne({
134
+ documentId,
135
+ fields: ["id"],
136
+ status
137
+ });
138
+ const relatedEntries = await search(entity.id, status, ct);
139
+ let publishedDocumentIds = [];
140
+ if (status === "draft") {
141
+ const entityPub = await strapi2.documents(contentType).findOne({
142
+ documentId,
143
+ fields: ["id"],
144
+ status: "published"
145
+ });
146
+ if (entityPub) {
147
+ const relatedEntriesPub = await search(entityPub.id, status, ct);
148
+ publishedDocumentIds = (await Object.entries(relatedEntriesPub).reduce(async (acc, [
149
+ uid,
150
+ { items }
151
+ ]) => {
152
+ const prev = await acc;
153
+ const entries = await strapi2.documents(uid).findMany({
154
+ filters: { id: { $in: items } },
155
+ status: "published",
156
+ fields: ["documentId"]
157
+ });
158
+ return prev.concat(entries);
159
+ }, [])).map(({ documentId: dId }) => dId);
160
+ }
161
+ }
162
+ const result = await Object.entries(relatedEntries).reduce(async (acc, [
163
+ uid,
164
+ { items, info }
165
+ ]) => {
166
+ const prev = await acc;
167
+ const entries = (await strapi2.documents(uid).findMany({
168
+ filters: { id: { $in: items } },
169
+ status
170
+ })).map((entry) => ({
171
+ uid,
172
+ contentTypeDisplayName: info.displayName,
173
+ title: entry.title || entry.name,
174
+ documentId: entry.documentId,
175
+ isPublished: status === "published" || publishedDocumentIds.includes(entry.documentId)
176
+ }));
177
+ return prev.concat(entries);
178
+ }, []);
179
+ return result;
180
+ }
181
+ });
182
+ const services = {
183
+ service
184
+ };
185
+ const index = {
186
+ bootstrap,
187
+ destroy,
188
+ register,
189
+ config,
190
+ controllers,
191
+ contentTypes,
192
+ middlewares,
193
+ policies,
194
+ routes,
195
+ services
196
+ };
197
+ module.exports = index;
198
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../server/src/bootstrap.js","../../server/src/destroy.js","../../server/src/register.js","../../server/src/config/index.js","../../server/src/content-types/index.js","../../server/src/controllers/controller.js","../../server/src/controllers/index.js","../../server/src/middlewares/index.js","../../server/src/policies/index.js","../../server/src/routes/admin.js","../../server/src/routes/index.js","../../server/src/services/service.js","../../server/src/services/index.js","../../server/src/index.js"],"sourcesContent":["const bootstrap = ({ strapi }) => {\n // bootstrap phase\n};\n\nexport default bootstrap;\n","const destroy = ({ strapi }) => {\n // destroy phase\n};\n\nexport default destroy;\n","const register = ({ strapi }) => {\n // register phase\n};\n\nexport default register;\n","export default {\n default: {},\n validator() {},\n};\n","export default {};\n","const controller = ({ strapi }) => ({\n async getunirelations(ctx) {\n const { id, contentType, status } = ctx.request.params;\n const response = await strapi.service('plugin::i-relate-to-this.service').getunirelations(contentType, id, status);\n if (!response) {\n ctx.body = { success: false };\n return;\n }\n ctx.body = {\n success: true,\n items: response,\n };\n },\n});\n\nexport default controller;\n","import controller from './controller';\n\nexport default {\n controller,\n};\n","export default {};\n","export default {};\n","export default [\n {\n method: 'GET',\n path: '/list/:contentType/:id/:status',\n handler: 'controller.getunirelations',\n }\n];\n","import adminRoutes from './admin';\n\nconst routes = {\n admin: {\n type: 'admin',\n routes: adminRoutes,\n },\n};\n\nexport default routes;\n","\nimport snakeCase from 'lodash.snakecase';\nimport mergeWith from 'lodash.mergewith';\n\nfunction customizer(objValue, srcValue) {\n if (Array.isArray(objValue)) {\n // we hard assume only one item is added to the array\n return objValue.includes(srcValue[0]) ? objValue : objValue.concat(srcValue[0]);\n }\n return undefined;\n}\n\nconst search = async (id, status, parent) => {\n const modelsWithRelationsToMe = Object\n .values({ ...strapi.contentTypes, ...strapi.components })\n .reduce((total, model) => {\n const {\n attributes,\n } = model;\n const relations = Object.entries(attributes).reduce((acc, [key, {\n type,\n target,\n inversedBy,\n mappedBy,\n component,\n components,\n }]) => {\n // localizations is always a relation to self\n if (key === 'localizations') {\n return acc;\n }\n if (\n // only uni relations\n !(type === 'relation' && target === parent.uid && !inversedBy && !mappedBy)\n && !(type === 'component' && component === parent.uid)\n && !(type === 'dynamiczone' && components?.includes(parent.uid))\n ) {\n return acc;\n }\n return acc.concat({\n key,\n type,\n ...model,\n });\n }, []);\n return total.concat(relations);\n }, []);\n\n const lookups = modelsWithRelationsToMe.reduce(async (acc, {\n key,\n type,\n ...model\n }) => {\n const {\n collectionName,\n modelName,\n } = model;\n const relationIdColumn = type === 'relation' ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(\n snakeCase(modelName),\n ) : 'entity_id';\n const parentIdColumn = type === 'relation' ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(\n snakeCase(parent.modelName),\n ) : 'cmp_id';\n const tableName = type === 'relation'\n ? `${snakeCase(`${collectionName} ${key} lnk`)}`\n : `${collectionName}_cmps`; // not snaked cased!\n\n // find this item in the db\n // console.log(`getting ${relationIdColumn} for ${parentIdColumn} ${id} from ${tableName}`);\n const relationsInTable = await strapi.db.getConnection(tableName).where({\n [parentIdColumn]: id,\n ...(type === 'relation' ? {} : {\n component_type: parent.uid,\n }),\n });\n\n // not related in this db, skip\n if (relationsInTable.length === 0) {\n return acc;\n }\n\n const itemsForThisCollection = relationsInTable.reduce(async (deepacc, item) => {\n const prev = await deepacc;\n // we've reached our endpoint if:\n if (model.uid.startsWith('api::') || model.uid.startsWith('plugin::')) {\n // make sure we dont have duplicates\n return mergeWith(prev, {\n [model.uid]: {\n info: model.info,\n items: [item[relationIdColumn]],\n },\n }, customizer);\n }\n // still a component, keep searching deeper\n const next = await search(item[relationIdColumn], status, model);\n return mergeWith(prev, next, customizer);\n }, {});\n\n // acc is the list of models, add the current found model ids\n return mergeWith(await acc, await itemsForThisCollection, customizer);\n }, {});\n\n return lookups;\n};\n\nconst service = ({ strapi }) => ({\n async getunirelations(contentType, documentId, status = 'draft') {\n console.log('running service');\n const ct = strapi.contentType(contentType);\n\n // find the db id by documentId\n const entity = await strapi.documents(contentType).findOne({\n documentId,\n fields: ['id'],\n status,\n });\n // go fetch all related items to me\n const relatedEntries = await search(entity.id, status, ct);\n\n let publishedDocumentIds = [];\n if (status === 'draft') {\n // find the db id by documentId\n const entityPub = await strapi.documents(contentType).findOne({\n documentId,\n fields: ['id'],\n status: 'published',\n });\n\n // current document could be draft and have no published version\n if (entityPub) {\n // go fetch all related items to me in published\n const relatedEntriesPub = await search(entityPub.id, status, ct);\n publishedDocumentIds = (await Object.entries(relatedEntriesPub).reduce(async (acc, [\n uid,\n { items },\n ]) => {\n const prev = await acc;\n const entries = (await strapi.documents(uid).findMany({\n filters: { id: { $in: items } },\n status: 'published',\n fields: ['documentId'],\n }));\n // add all entries for all models together\n return prev.concat(entries);\n }, [])).map(({ documentId: dId }) => dId);\n }\n }\n\n // convert results into a list of elements for the UI\n const result = await Object.entries(relatedEntries).reduce(async (acc, [\n uid,\n { items, info },\n ]) => {\n const prev = await acc;\n // // find all entries for this model\n const entries = (await strapi.documents(uid).findMany({\n filters: { id: { $in: items } },\n status,\n }))\n // create an output for the UI\n .map((entry) => ({\n uid,\n contentTypeDisplayName: info.displayName,\n title: entry.title || entry.name,\n documentId: entry.documentId,\n isPublished: status === 'published' || publishedDocumentIds.includes(entry.documentId),\n }));\n // add all entries for all models together\n return prev.concat(entries);\n }, []);\n\n return result;\n },\n});\n\nexport default service;\n","import service from './service';\n\nexport default {\n service,\n};\n","/**\n * Application methods\n */\nimport bootstrap from './bootstrap';\nimport destroy from './destroy';\nimport register from './register';\n\n/**\n * Plugin server methods\n */\nimport config from './config';\nimport contentTypes from './content-types';\nimport controllers from './controllers';\nimport middlewares from './middlewares';\nimport policies from './policies';\nimport routes from './routes';\nimport services from './services';\n\nexport default {\n bootstrap,\n destroy,\n register,\n\n config,\n controllers,\n contentTypes,\n middlewares,\n policies,\n routes,\n services,\n};\n"],"names":["strapi","snakeCase","mergeWith"],"mappings":"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"}
@@ -0,0 +1,196 @@
1
+ import snakeCase from "lodash.snakecase";
2
+ import mergeWith from "lodash.mergewith";
3
+ const bootstrap = ({ strapi: strapi2 }) => {
4
+ };
5
+ const destroy = ({ strapi: strapi2 }) => {
6
+ };
7
+ const register = ({ strapi: strapi2 }) => {
8
+ };
9
+ const config = {
10
+ default: {},
11
+ validator() {
12
+ }
13
+ };
14
+ const contentTypes = {};
15
+ const controller = ({ strapi: strapi2 }) => ({
16
+ async getunirelations(ctx) {
17
+ const { id, contentType, status } = ctx.request.params;
18
+ const response = await strapi2.service("plugin::i-relate-to-this.service").getunirelations(contentType, id, status);
19
+ if (!response) {
20
+ ctx.body = { success: false };
21
+ return;
22
+ }
23
+ ctx.body = {
24
+ success: true,
25
+ items: response
26
+ };
27
+ }
28
+ });
29
+ const controllers = {
30
+ controller
31
+ };
32
+ const middlewares = {};
33
+ const policies = {};
34
+ const adminRoutes = [
35
+ {
36
+ method: "GET",
37
+ path: "/list/:contentType/:id/:status",
38
+ handler: "controller.getunirelations"
39
+ }
40
+ ];
41
+ const routes = {
42
+ admin: {
43
+ type: "admin",
44
+ routes: adminRoutes
45
+ }
46
+ };
47
+ function customizer(objValue, srcValue) {
48
+ if (Array.isArray(objValue)) {
49
+ return objValue.includes(srcValue[0]) ? objValue : objValue.concat(srcValue[0]);
50
+ }
51
+ return void 0;
52
+ }
53
+ const search = async (id, status, parent) => {
54
+ const modelsWithRelationsToMe = Object.values({ ...strapi.contentTypes, ...strapi.components }).reduce((total, model) => {
55
+ const {
56
+ attributes
57
+ } = model;
58
+ const relations = Object.entries(attributes).reduce((acc, [key, {
59
+ type,
60
+ target,
61
+ inversedBy,
62
+ mappedBy,
63
+ component,
64
+ components
65
+ }]) => {
66
+ if (key === "localizations") {
67
+ return acc;
68
+ }
69
+ if (
70
+ // only uni relations
71
+ !(type === "relation" && target === parent.uid && !inversedBy && !mappedBy) && !(type === "component" && component === parent.uid) && !(type === "dynamiczone" && components?.includes(parent.uid))
72
+ ) {
73
+ return acc;
74
+ }
75
+ return acc.concat({
76
+ key,
77
+ type,
78
+ ...model
79
+ });
80
+ }, []);
81
+ return total.concat(relations);
82
+ }, []);
83
+ const lookups = modelsWithRelationsToMe.reduce(async (acc, {
84
+ key,
85
+ type,
86
+ ...model
87
+ }) => {
88
+ const {
89
+ collectionName,
90
+ modelName
91
+ } = model;
92
+ const relationIdColumn = type === "relation" ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(
93
+ snakeCase(modelName)
94
+ ) : "entity_id";
95
+ const parentIdColumn = type === "relation" ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(
96
+ snakeCase(parent.modelName)
97
+ ) : "cmp_id";
98
+ const tableName = type === "relation" ? `${snakeCase(`${collectionName} ${key} lnk`)}` : `${collectionName}_cmps`;
99
+ const relationsInTable = await strapi.db.getConnection(tableName).where({
100
+ [parentIdColumn]: id,
101
+ ...type === "relation" ? {} : {
102
+ component_type: parent.uid
103
+ }
104
+ });
105
+ if (relationsInTable.length === 0) {
106
+ return acc;
107
+ }
108
+ const itemsForThisCollection = relationsInTable.reduce(async (deepacc, item) => {
109
+ const prev = await deepacc;
110
+ if (model.uid.startsWith("api::") || model.uid.startsWith("plugin::")) {
111
+ return mergeWith(prev, {
112
+ [model.uid]: {
113
+ info: model.info,
114
+ items: [item[relationIdColumn]]
115
+ }
116
+ }, customizer);
117
+ }
118
+ const next = await search(item[relationIdColumn], status, model);
119
+ return mergeWith(prev, next, customizer);
120
+ }, {});
121
+ return mergeWith(await acc, await itemsForThisCollection, customizer);
122
+ }, {});
123
+ return lookups;
124
+ };
125
+ const service = ({ strapi: strapi2 }) => ({
126
+ async getunirelations(contentType, documentId, status = "draft") {
127
+ console.log("running service");
128
+ const ct = strapi2.contentType(contentType);
129
+ const entity = await strapi2.documents(contentType).findOne({
130
+ documentId,
131
+ fields: ["id"],
132
+ status
133
+ });
134
+ const relatedEntries = await search(entity.id, status, ct);
135
+ let publishedDocumentIds = [];
136
+ if (status === "draft") {
137
+ const entityPub = await strapi2.documents(contentType).findOne({
138
+ documentId,
139
+ fields: ["id"],
140
+ status: "published"
141
+ });
142
+ if (entityPub) {
143
+ const relatedEntriesPub = await search(entityPub.id, status, ct);
144
+ publishedDocumentIds = (await Object.entries(relatedEntriesPub).reduce(async (acc, [
145
+ uid,
146
+ { items }
147
+ ]) => {
148
+ const prev = await acc;
149
+ const entries = await strapi2.documents(uid).findMany({
150
+ filters: { id: { $in: items } },
151
+ status: "published",
152
+ fields: ["documentId"]
153
+ });
154
+ return prev.concat(entries);
155
+ }, [])).map(({ documentId: dId }) => dId);
156
+ }
157
+ }
158
+ const result = await Object.entries(relatedEntries).reduce(async (acc, [
159
+ uid,
160
+ { items, info }
161
+ ]) => {
162
+ const prev = await acc;
163
+ const entries = (await strapi2.documents(uid).findMany({
164
+ filters: { id: { $in: items } },
165
+ status
166
+ })).map((entry) => ({
167
+ uid,
168
+ contentTypeDisplayName: info.displayName,
169
+ title: entry.title || entry.name,
170
+ documentId: entry.documentId,
171
+ isPublished: status === "published" || publishedDocumentIds.includes(entry.documentId)
172
+ }));
173
+ return prev.concat(entries);
174
+ }, []);
175
+ return result;
176
+ }
177
+ });
178
+ const services = {
179
+ service
180
+ };
181
+ const index = {
182
+ bootstrap,
183
+ destroy,
184
+ register,
185
+ config,
186
+ controllers,
187
+ contentTypes,
188
+ middlewares,
189
+ policies,
190
+ routes,
191
+ services
192
+ };
193
+ export {
194
+ index as default
195
+ };
196
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../../server/src/bootstrap.js","../../server/src/destroy.js","../../server/src/register.js","../../server/src/config/index.js","../../server/src/content-types/index.js","../../server/src/controllers/controller.js","../../server/src/controllers/index.js","../../server/src/middlewares/index.js","../../server/src/policies/index.js","../../server/src/routes/admin.js","../../server/src/routes/index.js","../../server/src/services/service.js","../../server/src/services/index.js","../../server/src/index.js"],"sourcesContent":["const bootstrap = ({ strapi }) => {\n // bootstrap phase\n};\n\nexport default bootstrap;\n","const destroy = ({ strapi }) => {\n // destroy phase\n};\n\nexport default destroy;\n","const register = ({ strapi }) => {\n // register phase\n};\n\nexport default register;\n","export default {\n default: {},\n validator() {},\n};\n","export default {};\n","const controller = ({ strapi }) => ({\n async getunirelations(ctx) {\n const { id, contentType, status } = ctx.request.params;\n const response = await strapi.service('plugin::i-relate-to-this.service').getunirelations(contentType, id, status);\n if (!response) {\n ctx.body = { success: false };\n return;\n }\n ctx.body = {\n success: true,\n items: response,\n };\n },\n});\n\nexport default controller;\n","import controller from './controller';\n\nexport default {\n controller,\n};\n","export default {};\n","export default {};\n","export default [\n {\n method: 'GET',\n path: '/list/:contentType/:id/:status',\n handler: 'controller.getunirelations',\n }\n];\n","import adminRoutes from './admin';\n\nconst routes = {\n admin: {\n type: 'admin',\n routes: adminRoutes,\n },\n};\n\nexport default routes;\n","\nimport snakeCase from 'lodash.snakecase';\nimport mergeWith from 'lodash.mergewith';\n\nfunction customizer(objValue, srcValue) {\n if (Array.isArray(objValue)) {\n // we hard assume only one item is added to the array\n return objValue.includes(srcValue[0]) ? objValue : objValue.concat(srcValue[0]);\n }\n return undefined;\n}\n\nconst search = async (id, status, parent) => {\n const modelsWithRelationsToMe = Object\n .values({ ...strapi.contentTypes, ...strapi.components })\n .reduce((total, model) => {\n const {\n attributes,\n } = model;\n const relations = Object.entries(attributes).reduce((acc, [key, {\n type,\n target,\n inversedBy,\n mappedBy,\n component,\n components,\n }]) => {\n // localizations is always a relation to self\n if (key === 'localizations') {\n return acc;\n }\n if (\n // only uni relations\n !(type === 'relation' && target === parent.uid && !inversedBy && !mappedBy)\n && !(type === 'component' && component === parent.uid)\n && !(type === 'dynamiczone' && components?.includes(parent.uid))\n ) {\n return acc;\n }\n return acc.concat({\n key,\n type,\n ...model,\n });\n }, []);\n return total.concat(relations);\n }, []);\n\n const lookups = modelsWithRelationsToMe.reduce(async (acc, {\n key,\n type,\n ...model\n }) => {\n const {\n collectionName,\n modelName,\n } = model;\n const relationIdColumn = type === 'relation' ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(\n snakeCase(modelName),\n ) : 'entity_id';\n const parentIdColumn = type === 'relation' ? strapi.db.metadata.identifiers.getJoinColumnAttributeIdName(\n snakeCase(parent.modelName),\n ) : 'cmp_id';\n const tableName = type === 'relation'\n ? `${snakeCase(`${collectionName} ${key} lnk`)}`\n : `${collectionName}_cmps`; // not snaked cased!\n\n // find this item in the db\n // console.log(`getting ${relationIdColumn} for ${parentIdColumn} ${id} from ${tableName}`);\n const relationsInTable = await strapi.db.getConnection(tableName).where({\n [parentIdColumn]: id,\n ...(type === 'relation' ? {} : {\n component_type: parent.uid,\n }),\n });\n\n // not related in this db, skip\n if (relationsInTable.length === 0) {\n return acc;\n }\n\n const itemsForThisCollection = relationsInTable.reduce(async (deepacc, item) => {\n const prev = await deepacc;\n // we've reached our endpoint if:\n if (model.uid.startsWith('api::') || model.uid.startsWith('plugin::')) {\n // make sure we dont have duplicates\n return mergeWith(prev, {\n [model.uid]: {\n info: model.info,\n items: [item[relationIdColumn]],\n },\n }, customizer);\n }\n // still a component, keep searching deeper\n const next = await search(item[relationIdColumn], status, model);\n return mergeWith(prev, next, customizer);\n }, {});\n\n // acc is the list of models, add the current found model ids\n return mergeWith(await acc, await itemsForThisCollection, customizer);\n }, {});\n\n return lookups;\n};\n\nconst service = ({ strapi }) => ({\n async getunirelations(contentType, documentId, status = 'draft') {\n console.log('running service');\n const ct = strapi.contentType(contentType);\n\n // find the db id by documentId\n const entity = await strapi.documents(contentType).findOne({\n documentId,\n fields: ['id'],\n status,\n });\n // go fetch all related items to me\n const relatedEntries = await search(entity.id, status, ct);\n\n let publishedDocumentIds = [];\n if (status === 'draft') {\n // find the db id by documentId\n const entityPub = await strapi.documents(contentType).findOne({\n documentId,\n fields: ['id'],\n status: 'published',\n });\n\n // current document could be draft and have no published version\n if (entityPub) {\n // go fetch all related items to me in published\n const relatedEntriesPub = await search(entityPub.id, status, ct);\n publishedDocumentIds = (await Object.entries(relatedEntriesPub).reduce(async (acc, [\n uid,\n { items },\n ]) => {\n const prev = await acc;\n const entries = (await strapi.documents(uid).findMany({\n filters: { id: { $in: items } },\n status: 'published',\n fields: ['documentId'],\n }));\n // add all entries for all models together\n return prev.concat(entries);\n }, [])).map(({ documentId: dId }) => dId);\n }\n }\n\n // convert results into a list of elements for the UI\n const result = await Object.entries(relatedEntries).reduce(async (acc, [\n uid,\n { items, info },\n ]) => {\n const prev = await acc;\n // // find all entries for this model\n const entries = (await strapi.documents(uid).findMany({\n filters: { id: { $in: items } },\n status,\n }))\n // create an output for the UI\n .map((entry) => ({\n uid,\n contentTypeDisplayName: info.displayName,\n title: entry.title || entry.name,\n documentId: entry.documentId,\n isPublished: status === 'published' || publishedDocumentIds.includes(entry.documentId),\n }));\n // add all entries for all models together\n return prev.concat(entries);\n }, []);\n\n return result;\n },\n});\n\nexport default service;\n","import service from './service';\n\nexport default {\n service,\n};\n","/**\n * Application methods\n */\nimport bootstrap from './bootstrap';\nimport destroy from './destroy';\nimport register from './register';\n\n/**\n * Plugin server methods\n */\nimport config from './config';\nimport contentTypes from './content-types';\nimport controllers from './controllers';\nimport middlewares from './middlewares';\nimport policies from './policies';\nimport routes from './routes';\nimport services from './services';\n\nexport default {\n bootstrap,\n destroy,\n register,\n\n config,\n controllers,\n contentTypes,\n middlewares,\n policies,\n routes,\n services,\n};\n"],"names":["strapi"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "version": "0.0.0",
3
+ "keywords": [],
4
+ "type": "commonjs",
5
+ "exports": {
6
+ "./package.json": "./package.json",
7
+ "./strapi-admin": {
8
+ "source": "./admin/src/index.js",
9
+ "import": "./dist/admin/index.mjs",
10
+ "require": "./dist/admin/index.js",
11
+ "default": "./dist/admin/index.js"
12
+ },
13
+ "./strapi-server": {
14
+ "source": "./server/src/index.js",
15
+ "import": "./dist/server/index.mjs",
16
+ "require": "./dist/server/index.js",
17
+ "default": "./dist/server/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "strapi-plugin build",
25
+ "watch": "strapi-plugin watch",
26
+ "watch:link": "strapi-plugin watch:link",
27
+ "verify": "strapi-plugin verify"
28
+ },
29
+ "dependencies": {
30
+ "@strapi/design-system": "^2.0.0-rc.29",
31
+ "@strapi/icons": "^2.0.0-rc.29",
32
+ "react-intl": "^7.1.11",
33
+ "lodash.mergewith": "^4.6.2",
34
+ "lodash.snakecase": "^4.1.1"
35
+ },
36
+ "devDependencies": {
37
+ "@strapi/strapi": "^5.23.4",
38
+ "@strapi/sdk-plugin": "^5.3.2",
39
+ "prettier": "^3.6.2",
40
+ "react": "^18.3.1",
41
+ "react-dom": "^18.3.1",
42
+ "react-router-dom": "^6.30.1",
43
+ "styled-components": "^6.1.19"
44
+ },
45
+ "peerDependencies": {
46
+ "@strapi/strapi": "^5.23.4",
47
+ "@strapi/sdk-plugin": "^5.3.2",
48
+ "react": "^18.3.1",
49
+ "react-dom": "^18.3.1",
50
+ "react-router-dom": "^6.30.1",
51
+ "styled-components": "^6.1.19"
52
+ },
53
+ "strapi": {
54
+ "kind": "plugin",
55
+ "name": "i-relate-to-this",
56
+ "displayName": "I relate to this",
57
+ "description": "List unidirectional relations (in components) to the current entity"
58
+ },
59
+ "name": "strapi-plugin-i-relate-to-this",
60
+ "description": "List unidirectional relations (in components) to the current entity",
61
+ "license": "MIT",
62
+ "author": "Laurens Kling <laurens@goedideemedia.nl>"
63
+ }