├── playground
├── src
│ ├── api
│ │ ├── .gitkeep
│ │ ├── temp
│ │ │ ├── routes
│ │ │ │ └── temp.ts
│ │ │ ├── services
│ │ │ │ └── temp.ts
│ │ │ ├── controllers
│ │ │ │ └── temp.ts
│ │ │ └── content-types
│ │ │ │ └── temp
│ │ │ │ └── schema.json
│ │ ├── about
│ │ │ ├── routes
│ │ │ │ └── about.ts
│ │ │ ├── services
│ │ │ │ └── about.ts
│ │ │ ├── controllers
│ │ │ │ └── about.ts
│ │ │ └── content-types
│ │ │ │ └── about
│ │ │ │ └── schema.json
│ │ ├── author
│ │ │ ├── routes
│ │ │ │ └── author.ts
│ │ │ ├── services
│ │ │ │ └── author.ts
│ │ │ ├── controllers
│ │ │ │ └── author.ts
│ │ │ └── content-types
│ │ │ │ └── author
│ │ │ │ └── schema.json
│ │ ├── global
│ │ │ ├── routes
│ │ │ │ └── global.ts
│ │ │ ├── services
│ │ │ │ └── global.ts
│ │ │ ├── controllers
│ │ │ │ └── global.ts
│ │ │ └── content-types
│ │ │ │ └── global
│ │ │ │ └── schema.json
│ │ ├── article
│ │ │ ├── routes
│ │ │ │ └── article.ts
│ │ │ ├── services
│ │ │ │ └── article.ts
│ │ │ ├── controllers
│ │ │ │ └── article.ts
│ │ │ └── content-types
│ │ │ │ └── article
│ │ │ │ └── schema.json
│ │ ├── category
│ │ │ ├── routes
│ │ │ │ └── category.ts
│ │ │ ├── services
│ │ │ │ └── category.ts
│ │ │ ├── controllers
│ │ │ │ └── category.ts
│ │ │ └── content-types
│ │ │ │ └── category
│ │ │ │ └── schema.json
│ │ └── subscribed-user
│ │ │ ├── routes
│ │ │ └── subscribed-user.ts
│ │ │ ├── services
│ │ │ └── subscribed-user.ts
│ │ │ ├── controllers
│ │ │ └── subscribed-user.ts
│ │ │ └── content-types
│ │ │ └── subscribed-user
│ │ │ └── schema.json
│ ├── extensions
│ │ └── .gitkeep
│ ├── components
│ │ └── shared
│ │ │ ├── rich-text.json
│ │ │ ├── quote.json
│ │ │ ├── media.json
│ │ │ ├── slider.json
│ │ │ └── seo.json
│ ├── admin
│ │ ├── vite.config.ts
│ │ ├── tsconfig.json
│ │ └── app.example.tsx
│ ├── index.ts
│ └── protected-populate
│ │ └── index.json
├── public
│ ├── uploads
│ │ └── .gitkeep
│ └── robots.txt
├── database
│ └── migrations
│ │ └── .gitkeep
├── config
│ ├── plugins.ts
│ ├── api.ts
│ ├── server.ts
│ ├── middlewares.ts
│ ├── admin.ts
│ └── database.ts
├── favicon.png
├── .env.example
├── package.json
├── tsconfig.json
├── .gitignore
├── README.md
└── scripts
│ └── seed.js
├── strapi-plugin-protected-populate
├── .eslintignore
├── .prettierignore
├── server
│ └── src
│ │ ├── policies
│ │ └── index.js
│ │ ├── content-types
│ │ └── index.js
│ │ ├── destroy.js
│ │ ├── services
│ │ ├── index.js
│ │ └── data.js
│ │ ├── controllers
│ │ ├── index.js
│ │ └── data.js
│ │ ├── middlewares
│ │ ├── index.js
│ │ ├── protect.js
│ │ └── helpers
│ │ │ └── protect-route.js
│ │ ├── config
│ │ └── index.js
│ │ ├── bootstrap.js
│ │ ├── index.js
│ │ ├── routes
│ │ └── index.js
│ │ └── register.js
├── .prettierrc
├── admin
│ └── src
│ │ ├── permissions.js
│ │ ├── components
│ │ ├── PluginIcon
│ │ │ └── index.jsx
│ │ ├── ListRow
│ │ │ ├── BoxWrapper.jsx
│ │ │ ├── DisplayedType.jsx
│ │ │ └── index.jsx
│ │ ├── Initializer
│ │ │ └── index.jsx
│ │ ├── Tr
│ │ │ └── index.jsx
│ │ ├── List
│ │ │ ├── BoxWrapper.jsx
│ │ │ └── index.jsx
│ │ ├── DynamicZoneList
│ │ │ ├── BoxWrapper.jsx
│ │ │ ├── index.jsx
│ │ │ └── SelectRender.jsx
│ │ ├── ComponentList
│ │ │ └── index.jsx
│ │ ├── RoleAccordion
│ │ │ └── index.jsx
│ │ ├── AttributeIcon
│ │ │ └── index.jsx
│ │ ├── SelectRender
│ │ │ └── index.jsx
│ │ └── RouteAccordion
│ │ │ └── index.jsx
│ │ ├── pluginId.js
│ │ ├── pages
│ │ ├── App
│ │ │ └── index.jsx
│ │ └── HomePage
│ │ │ └── index.jsx
│ │ ├── index.js
│ │ ├── utils
│ │ └── serverRestartWatcher.js
│ │ └── icons
│ │ └── Curve.jsx
├── .editorconfig
├── package.json
├── .gitignore
└── README.md
├── package.json
├── License.md
├── .gitignore
└── README.md
/playground/src/api/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/public/uploads/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/src/extensions/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/database/migrations/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/playground/config/plugins.ts:
--------------------------------------------------------------------------------
1 | export default () => ({});
2 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | coverage
3 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/policies/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {};
4 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/content-types/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {};
4 |
--------------------------------------------------------------------------------
/playground/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strapi-community/strapi-plugin-protected-populate/HEAD/playground/favicon.png
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/destroy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = ({ strapi }) => {
4 | // destroy phase
5 | };
6 |
--------------------------------------------------------------------------------
/playground/config/api.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | rest: {
3 | defaultLimit: 25,
4 | maxLimit: 100,
5 | withCount: true,
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/playground/public/robots.txt:
--------------------------------------------------------------------------------
1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines:
2 | # User-Agent: *
3 | # Disallow: /
4 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/services/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const data = require('./data');
4 |
5 | module.exports = {
6 | data,
7 | };
8 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/controllers/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const data = require('./data');
4 |
5 | module.exports = {
6 | data,
7 | };
8 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "tabWidth": 2,
4 | "printWidth": 100,
5 | "singleQuote": true,
6 | "trailingComma": "es5"
7 | }
8 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/middlewares/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const protect = require('./protect');
4 |
5 | module.exports = {
6 | protect,
7 | };
8 |
--------------------------------------------------------------------------------
/playground/src/api/temp/routes/temp.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * temp router
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreRouter('api::temp.temp');
8 |
--------------------------------------------------------------------------------
/playground/config/server.ts:
--------------------------------------------------------------------------------
1 | export default ({ env }) => ({
2 | host: env('HOST', '0.0.0.0'),
3 | port: env.int('PORT', 1337),
4 | app: {
5 | keys: env.array('APP_KEYS'),
6 | },
7 | });
8 |
--------------------------------------------------------------------------------
/playground/src/api/about/routes/about.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * about router.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreRouter('api::about.about');
8 |
--------------------------------------------------------------------------------
/playground/src/api/temp/services/temp.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * temp service
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreService('api::temp.temp');
8 |
--------------------------------------------------------------------------------
/playground/src/api/about/services/about.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * about service.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreService('api::about.about');
8 |
--------------------------------------------------------------------------------
/playground/src/api/author/routes/author.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * author router.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreRouter('api::author.author');
8 |
--------------------------------------------------------------------------------
/playground/src/api/global/routes/global.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * global router.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreRouter('api::global.global');
8 |
--------------------------------------------------------------------------------
/playground/src/api/temp/controllers/temp.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * temp controller
3 | */
4 |
5 | import { factories } from '@strapi/strapi'
6 |
7 | export default factories.createCoreController('api::temp.temp');
8 |
--------------------------------------------------------------------------------
/playground/src/api/article/routes/article.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * article router.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreRouter('api::article.article');
8 |
--------------------------------------------------------------------------------
/playground/src/api/author/services/author.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * author service.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreService('api::author.author');
8 |
--------------------------------------------------------------------------------
/playground/src/api/global/services/global.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * global service.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreService('api::global.global');
8 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/permissions.js:
--------------------------------------------------------------------------------
1 | const pluginPermissions = {
2 | main: [{ action: 'plugin::protected-populate.read', subject: null }],
3 | };
4 | export default pluginPermissions;
5 |
--------------------------------------------------------------------------------
/playground/src/api/about/controllers/about.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * about controller
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreController('api::about.about');
8 |
--------------------------------------------------------------------------------
/playground/src/api/article/services/article.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * article service.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreService('api::article.article');
8 |
--------------------------------------------------------------------------------
/playground/src/api/category/routes/category.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * category router.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreRouter('api::category.category');
8 |
--------------------------------------------------------------------------------
/playground/src/api/author/controllers/author.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * author controller
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreController('api::author.author');
8 |
--------------------------------------------------------------------------------
/playground/src/api/category/services/category.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * category service.
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreService('api::category.category');
8 |
--------------------------------------------------------------------------------
/playground/src/api/global/controllers/global.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * global controller
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreController('api::global.global');
8 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/PluginIcon/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Lock } from '@strapi/icons';
3 |
4 | const PluginIcon = () => ;
5 | export default PluginIcon;
6 |
--------------------------------------------------------------------------------
/playground/.env.example:
--------------------------------------------------------------------------------
1 | HOST=0.0.0.0
2 | PORT=1337
3 | APP_KEYS="toBeModified1,toBeModified2"
4 | API_TOKEN_SALT=tobemodified
5 | ADMIN_JWT_SECRET=tobemodified
6 | TRANSFER_TOKEN_SALT=tobemodified
7 | JWT_SECRET=tobemodified
8 |
--------------------------------------------------------------------------------
/playground/src/api/article/controllers/article.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * article controller
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreController('api::article.article');
8 |
--------------------------------------------------------------------------------
/playground/src/api/category/controllers/category.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * category controller
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreController('api::category.category');
8 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/pluginId.js:
--------------------------------------------------------------------------------
1 | import pluginPkg from '../../package.json';
2 |
3 | const pluginId = pluginPkg.name.replace(/^(@[^-,.][\w,-]+\/|strapi-)plugin-/i, '');
4 |
5 | export default pluginId;
6 |
--------------------------------------------------------------------------------
/playground/src/api/subscribed-user/routes/subscribed-user.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * subscribed-user router
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreRouter('api::subscribed-user.subscribed-user');
8 |
--------------------------------------------------------------------------------
/playground/src/api/subscribed-user/services/subscribed-user.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * subscribed-user service
3 | */
4 |
5 | import { factories } from '@strapi/strapi';
6 |
7 | export default factories.createCoreService('api::subscribed-user.subscribed-user');
8 |
--------------------------------------------------------------------------------
/playground/src/api/subscribed-user/controllers/subscribed-user.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * subscribed-user controller
3 | */
4 |
5 | import { factories } from '@strapi/strapi'
6 |
7 | export default factories.createCoreController('api::subscribed-user.subscribed-user');
8 |
--------------------------------------------------------------------------------
/playground/config/middlewares.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | 'strapi::logger',
3 | 'strapi::errors',
4 | 'strapi::security',
5 | 'strapi::cors',
6 | 'strapi::poweredBy',
7 | 'strapi::query',
8 | 'strapi::body',
9 | 'strapi::session',
10 | 'strapi::favicon',
11 | 'strapi::public',
12 | ];
13 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/ListRow/BoxWrapper.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Wrapper
4 | *
5 | */
6 |
7 | import styled from 'styled-components';
8 | import { Box } from '@strapi/design-system';
9 |
10 | const BoxWrapper = styled(Box)`
11 | position: relative;
12 | `;
13 |
14 | export default BoxWrapper;
15 |
--------------------------------------------------------------------------------
/playground/src/components/shared/rich-text.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectionName": "components_shared_rich_texts",
3 | "info": {
4 | "displayName": "Rich text",
5 | "icon": "align-justify",
6 | "description": ""
7 | },
8 | "options": {},
9 | "attributes": {
10 | "body": {
11 | "type": "richtext"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/playground/src/components/shared/quote.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectionName": "components_shared_quotes",
3 | "info": {
4 | "displayName": "Quote",
5 | "icon": "indent"
6 | },
7 | "options": {},
8 | "attributes": {
9 | "title": {
10 | "type": "string"
11 | },
12 | "body": {
13 | "type": "text"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | default: ({ env }) => ({ ['auto-populate']: false }),
5 | validator: (config) => {
6 | if (typeof config['auto-populate'] !== 'boolean') {
7 | throw new Error('config["auto-populate"] has to be a boolean');
8 | }
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{package.json,*.yml}]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/playground/src/components/shared/media.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectionName": "components_shared_media",
3 | "info": {
4 | "displayName": "Media",
5 | "icon": "file-video"
6 | },
7 | "options": {},
8 | "attributes": {
9 | "file": {
10 | "allowedTypes": ["images", "files", "videos"],
11 | "type": "media",
12 | "multiple": false
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = async ({ strapi }) => {
4 | const actions = [
5 | {
6 | section: 'plugins',
7 | displayName: 'Read',
8 | uid: 'read',
9 | pluginName: 'protected-populate',
10 | },
11 | ];
12 |
13 | await strapi.admin.services.permission.actionProvider.registerMany(actions);
14 | };
15 |
--------------------------------------------------------------------------------
/playground/config/admin.ts:
--------------------------------------------------------------------------------
1 | export default ({ env }) => ({
2 | auth: {
3 | secret: env('ADMIN_JWT_SECRET'),
4 | },
5 | apiToken: {
6 | salt: env('API_TOKEN_SALT'),
7 | },
8 | transfer: {
9 | token: {
10 | salt: env('TRANSFER_TOKEN_SALT'),
11 | },
12 | },
13 | flags: {
14 | nps: env.bool('FLAG_NPS', true),
15 | promoteEE: env.bool('FLAG_PROMOTE_EE', true),
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/playground/src/admin/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig, type UserConfig } from 'vite';
2 |
3 | export default (config: UserConfig) => {
4 | // Important: always return the modified config
5 | if(config.build !== undefined ){
6 | //config.build.minify = false
7 | }
8 | return mergeConfig(config, {
9 | resolve: {
10 | alias: {
11 | '@': '/src',
12 | },
13 | },
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/playground/src/components/shared/slider.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectionName": "components_shared_sliders",
3 | "info": {
4 | "displayName": "Slider",
5 | "icon": "address-book",
6 | "description": ""
7 | },
8 | "options": {},
9 | "attributes": {
10 | "files": {
11 | "type": "media",
12 | "multiple": true,
13 | "required": false,
14 | "allowedTypes": ["images"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/Initializer/index.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Initializer
4 | *
5 | */
6 |
7 | import { useEffect, useRef } from 'react';
8 | import PropTypes from 'prop-types';
9 | import pluginId from '../../pluginId';
10 |
11 | const Initializer = ({ setPlugin }) => {
12 | const ref = useRef();
13 | ref.current = setPlugin;
14 |
15 | useEffect(() => {
16 | ref.current(pluginId);
17 | }, []);
18 |
19 | return null;
20 | };
21 |
22 | Initializer.propTypes = {
23 | setPlugin: PropTypes.func.isRequired,
24 | };
25 |
26 | export default Initializer;
27 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/controllers/data.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = ({ strapi }) => ({
4 | contentTypes(ctx) {
5 | ctx.body = strapi.plugin('protected-populate').service('data').getContentTypes();
6 | },
7 | routes(ctx) {
8 | ctx.body = strapi.plugin('protected-populate').service('data').getRoutes();
9 | },
10 | indexData(ctx) {
11 | ctx.body = strapi.plugin('protected-populate').service('data').indexData();
12 | },
13 | updateData(ctx) {
14 | ctx.body = strapi.plugin('protected-populate').service('data').updateData(ctx.request.body);
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/pages/App/index.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * This component is the skeleton around the actual pages, and should only
4 | * contain code that should be seen on all pages. (e.g. navigation bar)
5 | *
6 | */
7 |
8 | import * as React from 'react';
9 | import pluginPermissions from '../../permissions';
10 | import HomePage from '../HomePage';
11 | import { Page } from '@strapi/strapi/admin';
12 | const App = () => {
13 | return (
14 | (
15 |
16 | )
17 | );
18 | };
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/playground/src/components/shared/seo.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectionName": "components_shared_seos",
3 | "info": {
4 | "name": "Seo",
5 | "icon": "allergies",
6 | "displayName": "Seo",
7 | "description": ""
8 | },
9 | "options": {},
10 | "attributes": {
11 | "metaTitle": {
12 | "type": "string",
13 | "required": true
14 | },
15 | "metaDescription": {
16 | "type": "text",
17 | "required": true
18 | },
19 | "shareImage": {
20 | "type": "media",
21 | "multiple": false,
22 | "required": false,
23 | "allowedTypes": ["images"]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/playground/src/index.ts:
--------------------------------------------------------------------------------
1 | // import type { Core } from '@strapi/strapi';
2 |
3 | export default {
4 | /**
5 | * An asynchronous register function that runs before
6 | * your application is initialized.
7 | *
8 | * This gives you an opportunity to extend code.
9 | */
10 | register(/* { strapi }: { strapi: Core.Strapi } */) {},
11 |
12 | /**
13 | * An asynchronous bootstrap function that runs before
14 | * your application gets started.
15 | *
16 | * This gives you an opportunity to set up your data model,
17 | * run jobs, or perform some special logic.
18 | */
19 | bootstrap(/* { strapi }: { strapi: Core.Strapi } */) {},
20 | };
21 |
--------------------------------------------------------------------------------
/playground/src/admin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "Bundler",
6 | "useDefineForClassFields": true,
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "allowJs": false,
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "strict": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "resolveJsonModule": true,
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 | },
18 | "include": ["../plugins/**/admin/src/**/*", "./"],
19 | "exclude": ["node_modules/", "build/", "dist/", "**/*.test.ts"]
20 | }
21 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const register = require('./register');
4 | const bootstrap = require('./bootstrap');
5 | const destroy = require('./destroy');
6 | const config = require('./config');
7 | const contentTypes = require('./content-types');
8 | const controllers = require('./controllers');
9 | const routes = require('./routes');
10 | const middlewares = require('./middlewares');
11 | const policies = require('./policies');
12 | const services = require('./services');
13 |
14 | module.exports = {
15 | register,
16 | bootstrap,
17 | destroy,
18 | config,
19 | controllers,
20 | routes,
21 | services,
22 | contentTypes,
23 | policies,
24 | middlewares,
25 | };
26 |
--------------------------------------------------------------------------------
/playground/src/admin/app.example.tsx:
--------------------------------------------------------------------------------
1 | import type { StrapiApp } from '@strapi/admin/strapi-admin';
2 |
3 | export default {
4 | config: {
5 | locales: [
6 | // 'ar',
7 | // 'fr',
8 | // 'cs',
9 | // 'de',
10 | // 'dk',
11 | // 'es',
12 | // 'he',
13 | // 'id',
14 | // 'it',
15 | // 'ja',
16 | // 'ko',
17 | // 'ms',
18 | // 'nl',
19 | // 'no',
20 | // 'pl',
21 | // 'pt-BR',
22 | // 'pt',
23 | // 'ru',
24 | // 'sk',
25 | // 'sv',
26 | // 'th',
27 | // 'tr',
28 | // 'uk',
29 | // 'vi',
30 | // 'zh-Hans',
31 | // 'zh',
32 | ],
33 | },
34 | bootstrap(app: StrapiApp) {
35 | console.log(app);
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/playground/src/api/subscribed-user/content-types/subscribed-user/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "collectionType",
3 | "collectionName": "subscribed_users",
4 | "info": {
5 | "singularName": "subscribed-user",
6 | "pluralName": "subscribed-users",
7 | "displayName": "Subscribed Users",
8 | "description": ""
9 | },
10 | "options": {
11 | "draftAndPublish": false
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "user": {
16 | "type": "relation",
17 | "relation": "oneToOne",
18 | "target": "plugin::users-permissions.user"
19 | },
20 | "events": {
21 | "type": "enumeration",
22 | "enum": [
23 | "Blog",
24 | "Course"
25 | ]
26 | },
27 | "is_active": {
28 | "type": "boolean",
29 | "default": true,
30 | "required": true
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/playground/src/api/category/content-types/category/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "collectionType",
3 | "collectionName": "categories",
4 | "info": {
5 | "singularName": "category",
6 | "pluralName": "categories",
7 | "displayName": "Category",
8 | "description": "Organize your content into categories"
9 | },
10 | "options": {
11 | "draftAndPublish": false
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "name": {
16 | "type": "string"
17 | },
18 | "slug": {
19 | "type": "uid"
20 | },
21 | "articles": {
22 | "type": "relation",
23 | "relation": "oneToMany",
24 | "target": "api::article.article",
25 | "mappedBy": "category"
26 | },
27 | "description": {
28 | "type": "text"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/playground/src/api/about/content-types/about/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "singleType",
3 | "collectionName": "abouts",
4 | "info": {
5 | "singularName": "about",
6 | "pluralName": "abouts",
7 | "displayName": "About",
8 | "description": "Write about yourself and the content you create"
9 | },
10 | "options": {
11 | "draftAndPublish": false
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "title": {
16 | "type": "string"
17 | },
18 | "blocks": {
19 | "type": "dynamiczone",
20 | "components": [
21 | "shared.media",
22 | "shared.quote",
23 | "shared.rich-text",
24 | "shared.slider"
25 | ]
26 | },
27 | "is_active": {
28 | "type": "boolean"
29 | },
30 | "private": {
31 | "type": "string",
32 | "private": true
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/playground/src/api/author/content-types/author/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "collectionType",
3 | "collectionName": "authors",
4 | "info": {
5 | "singularName": "author",
6 | "pluralName": "authors",
7 | "displayName": "Author",
8 | "description": "Create authors for your content"
9 | },
10 | "options": {
11 | "draftAndPublish": false
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "name": {
16 | "type": "string"
17 | },
18 | "avatar": {
19 | "type": "media",
20 | "multiple": false,
21 | "required": false,
22 | "allowedTypes": ["images", "files", "videos"]
23 | },
24 | "email": {
25 | "type": "string"
26 | },
27 | "articles": {
28 | "type": "relation",
29 | "relation": "oneToMany",
30 | "target": "api::article.article",
31 | "mappedBy": "author"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/playground/src/api/global/content-types/global/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "singleType",
3 | "collectionName": "globals",
4 | "info": {
5 | "singularName": "global",
6 | "pluralName": "globals",
7 | "displayName": "Global",
8 | "description": "Define global settings"
9 | },
10 | "options": {
11 | "draftAndPublish": false
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "siteName": {
16 | "type": "string",
17 | "required": true
18 | },
19 | "favicon": {
20 | "type": "media",
21 | "multiple": false,
22 | "required": false,
23 | "allowedTypes": ["images", "files", "videos"]
24 | },
25 | "siteDescription": {
26 | "type": "text",
27 | "required": true
28 | },
29 | "defaultSeo": {
30 | "type": "component",
31 | "repeatable": false,
32 | "component": "shared.seo"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/playground/src/api/temp/content-types/temp/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "collectionType",
3 | "collectionName": "temps",
4 | "info": {
5 | "singularName": "temp",
6 | "pluralName": "temps",
7 | "displayName": "temp",
8 | "description": ""
9 | },
10 | "options": {
11 | "draftAndPublish": true
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "test": {
16 | "type": "dynamiczone",
17 | "components": [
18 | "shared.slider",
19 | "shared.seo",
20 | "shared.rich-text",
21 | "shared.quote",
22 | "shared.media"
23 | ],
24 | "pluginOptions": {}
25 | },
26 | "title": {
27 | "type": "string",
28 | "pluginOptions": {}
29 | },
30 | "is_active": {
31 | "pluginOptions": {},
32 | "type": "boolean"
33 | },
34 | "private": {
35 | "type": "string",
36 | "private": true
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/Tr/index.jsx:
--------------------------------------------------------------------------------
1 |
2 | import styled from 'styled-components';
3 |
4 | // Keep component-row for css specificity
5 | const Tr = styled.tr`
6 | &.component-row,
7 | &.dynamiczone-row {
8 | position: relative;
9 | border-top: none !important;
10 |
11 | table tr:first-child {
12 | border-top: none;
13 | }
14 |
15 | > td:first-of-type {
16 | padding: 0 0 0 ${20 / 16};
17 | position: relative;
18 |
19 | &::before {
20 | content: '';
21 | width: 0.5rem;
22 | height: calc(100%);
23 | position: absolute;
24 | top: -7px;
25 | left: 1.625rem;
26 | border-radius: 4px;
27 |
28 | ${({ theme }) => {
29 | return `background: ${theme.colors.neutral150};`;
30 | }}
31 | }
32 | }
33 | }
34 |
35 | &.dynamiczone-row > td:first-of-type {
36 | padding: 0;
37 | }
38 | `;
39 |
40 | export default Tr;
41 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/index.js:
--------------------------------------------------------------------------------
1 | import pluginPkg from '../../package.json';
2 | import pluginId from './pluginId';
3 | import Initializer from './components/Initializer';
4 | import PluginIcon from './components/PluginIcon';
5 | import pluginPermissions from './permissions';
6 |
7 | const name = pluginPkg.strapi.name;
8 |
9 | export default {
10 | register(app) {
11 | app.addMenuLink({
12 | to: `/plugins/${pluginId}`,
13 | icon: PluginIcon,
14 | intlLabel: {
15 | id: `${pluginId}.plugin.displayName`,
16 | defaultMessage: name,
17 | },
18 | permissions: pluginPermissions.main,
19 | Component: async () => {
20 | const component = await import('./pages/App');
21 |
22 | return component;
23 | },
24 | });
25 | app.registerPlugin({
26 | id: pluginId,
27 | initializer: Initializer,
28 | isReady: false,
29 | name,
30 | });
31 | },
32 |
33 | bootstrap(app) {},
34 | };
35 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/List/BoxWrapper.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Wrapper
4 | *
5 | */
6 | import { Box } from '@strapi/design-system';
7 | import styled from 'styled-components';
8 |
9 | const BoxWrapper = styled(Box)`
10 | table {
11 | width: 100%;
12 | white-space: nowrap;
13 | }
14 |
15 | thead {
16 | border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};
17 |
18 | tr {
19 | border-top: 0;
20 | }
21 | }
22 |
23 | tr {
24 | border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
25 |
26 | & td,
27 | & th {
28 | padding: ${({ theme }) => theme.spaces[4]};
29 | }
30 |
31 | & td:first-of-type,
32 | & th:first-of-type {
33 | padding: 0 ${({ theme }) => theme.spaces[1]};
34 | }
35 | }
36 |
37 | th,
38 | td {
39 | vertical-align: middle;
40 | text-align: left;
41 | color: ${({ theme }) => theme.colors.neutral600};
42 | outline-offset: -4px;
43 | }
44 | `;
45 |
46 | export default BoxWrapper;
47 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/ListRow/DisplayedType.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Typography } from '@strapi/design-system';
4 |
5 | const DisplayedType = ({ type, customField, repeatable }) => {
6 | let readableType = type;
7 |
8 | if (['integer', 'biginteger', 'float', 'decimal'].includes(type)) {
9 | readableType = 'number';
10 | } else if (['string'].includes(type)) {
11 | readableType = 'text';
12 | }
13 |
14 | if (customField) {
15 | return Custom field ;
16 | }
17 |
18 | return (
19 |
20 | {type}
21 |
22 | {repeatable && '(repeatable)'}
23 |
24 | );
25 | };
26 |
27 | DisplayedType.defaultProps = {
28 | customField: null,
29 | repeatable: false,
30 | };
31 |
32 | DisplayedType.propTypes = {
33 | type: PropTypes.string.isRequired,
34 | customField: PropTypes.string,
35 | repeatable: PropTypes.bool,
36 | };
37 |
38 | export default DisplayedType;
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@strapi-plugin-protected-populate/monorepo",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "Speed-up HTTP requests with LRU cache",
6 | "workspaces": [
7 | "./strapi-plugin-protected-populate",
8 | "./playground"
9 | ],
10 | "scripts": {
11 | "dev": "yarn workspace playground develop",
12 | "develop": "yarn workspace playground develop",
13 | "build": "yarn workspace playground build",
14 | "console": "yarn workspace playground strapi console",
15 | "strapi": "yarn workspace playground strapi",
16 | "plugin-build": "yarn workspace strapi-plugin-protected-populate strapi-plugin build",
17 | "verify": "yarn workspace strapi-plugin-protected-populate strapi-plugin verify",
18 | "watch": "yarn workspace strapi-plugin-protected-populate strapi-plugin watch",
19 | "watch:link": "yarn workspace strapi-plugin-protected-populate strapi-plugin watch:link"
20 | },
21 | "author": "Strapi Community Org",
22 | "license": "MIT",
23 | "dependencies": {}
24 | }
25 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/DynamicZoneList/BoxWrapper.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Wrapper
4 | *
5 | */
6 | import { Box } from '@strapi/design-system';
7 | import styled from 'styled-components';
8 |
9 | const BoxWrapper = styled(Box)`
10 | table {
11 | width: 100%;
12 | white-space: nowrap;
13 | }
14 |
15 | thead {
16 | border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};
17 |
18 | tr {
19 | border-top: 0;
20 | }
21 | }
22 |
23 | tr {
24 | border-top: 1px solid ${({ theme }) => theme.colors.neutral150};
25 |
26 | & td,
27 | & th {
28 | padding: ${({ theme }) => theme.spaces[4]};
29 | }
30 |
31 | & td:first-of-type,
32 | & th:first-of-type {
33 | padding: 0 ${({ theme }) => theme.spaces[1]};
34 | }
35 | }
36 |
37 | th,
38 | td {
39 | vertical-align: middle;
40 | text-align: left;
41 | color: ${({ theme }) => theme.colors.neutral600};
42 | outline-offset: -4px;
43 | }
44 | `;
45 |
46 | export default BoxWrapper;
47 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/middlewares/protect.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const { protectRoute } = require('./helpers/protect-route');
3 | module.exports = (config, { strapi }) => {
4 | // Add your own logic here.
5 | return async (ctx, next) => {
6 | //Info is parsed to and stingified to ensure none can do someting to the original object
7 | let info = JSON.parse(JSON.stringify(ctx.state.route.config['protected-populate']));
8 | if (info.roles !== undefined) {
9 | if (ctx.state.user === undefined) {
10 | info = info.roles['public'];
11 | } else {
12 | info = info.roles[ctx.state.user.role.type];
13 | }
14 | }
15 | if (
16 | typeof ctx.query.populate === 'undefined' &&
17 | typeof ctx.query.fields === 'undefined' &&
18 | strapi.plugin('protected-populate').config('auto-populate')
19 | ) {
20 | ctx.query.populate = info.populate;
21 | ctx.query.fields = info.fields;
22 | }
23 | ctx.query = protectRoute(ctx.query, info);
24 | await next();
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Boegie19
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground",
3 | "version": "0.1.0",
4 | "private": true,
5 | "description": "A Strapi application",
6 | "scripts": {
7 | "build": "strapi build",
8 | "deploy": "strapi deploy",
9 | "develop": "strapi develop",
10 | "seed:example": "node ./scripts/seed.js",
11 | "start": "strapi start",
12 | "strapi": "strapi"
13 | },
14 | "dependencies": {
15 | "@strapi/plugin-users-permissions": "5.6.0",
16 | "strapi-plugin-protected-populate": "2.0.0",
17 | "@strapi/strapi": "5.6.0",
18 | "better-sqlite3": "9.4.3",
19 | "fs-extra": "^10.0.0",
20 | "mime-types": "^2.1.27",
21 | "react": "^18.0.0",
22 | "react-dom": "^18.0.0",
23 | "react-router-dom": "^6.0.0",
24 | "styled-components": "^6.0.0"
25 | },
26 | "devDependencies": {
27 | "@types/node": "^20",
28 | "@types/react": "^18",
29 | "@types/react-dom": "^18",
30 | "typescript": "^5"
31 | },
32 | "engines": {
33 | "node": ">=18.0.0 <=20.x.x",
34 | "npm": ">=6.0.0"
35 | },
36 | "strapi": {
37 | "uuid": "5aec3647-afac-417c-8ebf-0986bfb46db0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "moduleResolution": "Node",
5 | "lib": ["ES2020"],
6 | "target": "ES2019",
7 | "strict": false,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "incremental": true,
11 | "esModuleInterop": true,
12 | "resolveJsonModule": true,
13 | "noEmitOnError": true,
14 | "noImplicitThis": true,
15 | "outDir": "dist",
16 | "rootDir": "."
17 | },
18 | "include": [
19 | // Include root files
20 | "./",
21 | // Include all ts files
22 | "./**/*.ts",
23 | // Include all js files
24 | "./**/*.js",
25 | // Force the JSON files in the src folder to be included
26 | "src/**/*.json"
27 | ],
28 |
29 | "exclude": [
30 | "node_modules/",
31 | "build/",
32 | "dist/",
33 | ".cache/",
34 | ".tmp/",
35 |
36 | // Do not include admin files in the server compilation
37 | "src/admin/",
38 | // Do not include test files
39 | "**/*.test.*",
40 | // Do not include plugins in the server compilation
41 | "src/plugins/**"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/services/data.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 |
5 | module.exports = ({ strapi }) => ({
6 | getRoutes() {
7 | const routes = strapi.server.router.stack;
8 | return routes.filter(
9 | (route) => route.path.startsWith('/api/')
10 | );
11 | },
12 | getContentTypes() {
13 | const contentTypes = strapi.get('content-types').keys();
14 | return contentTypes.filter(
15 | (contentType) => contentType.startsWith('api::') || contentType.startsWith('plugin::')
16 | );
17 | },
18 | indexData() {
19 | return strapi.plugin('protected-populate').protectedRoutes
20 | },
21 | updateData(body) {
22 | strapi.plugin('protected-populate').protectedRoutes = body;
23 | strapi.plugin('protected-populate').service('data').writePopulateFile(body);
24 | },
25 | writePopulateFile(routes) {
26 | if (!fs.existsSync(strapi.dirs.app.src + '/protected-populate')) {
27 | fs.mkdirSync(strapi.dirs.app.src + '/protected-populate', { recursive: true });
28 | }
29 | fs.writeFileSync(
30 | strapi.dirs.app.src + `/protected-populate/index.json`,
31 | JSON.stringify(routes, null, 3)
32 | );
33 | },
34 | });
35 |
--------------------------------------------------------------------------------
/playground/src/protected-populate/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "GET /api/articles": {
3 | "content-type": "api::article.article",
4 | "roles": {
5 | "authenticated": {
6 | "populate": {
7 | "author": {
8 | "fields": [
9 | "name",
10 | "email",
11 | "createdAt",
12 | "updatedAt",
13 | "id"
14 | ],
15 | "populate": {
16 | "articles": {
17 | "populate": {
18 | "category": {}
19 | }
20 | }
21 | }
22 | }
23 | },
24 | "fields": [
25 | "title",
26 | "description",
27 | "slug",
28 | "documentId",
29 | "publishedAt",
30 | "id",
31 | "updatedAt",
32 | "createdAt"
33 | ]
34 | },
35 | "public": {
36 | "populate": {},
37 | "fields": [
38 | "title"
39 | ]
40 | }
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/routes/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | method: 'GET',
4 | path: '/content-types',
5 | handler: 'data.contentTypes',
6 | config: {
7 | policies: [
8 | {
9 | name: 'admin::hasPermissions',
10 | config: { actions: ['plugin::protected-populate.read'] },
11 | },
12 | ],
13 | },
14 | },
15 | {
16 | method: 'GET',
17 | path: '/routes',
18 | handler: 'data.routes',
19 | config: {
20 | policies: [
21 | {
22 | name: 'admin::hasPermissions',
23 | config: { actions: ['plugin::protected-populate.read'] },
24 | },
25 | ],
26 | },
27 | },
28 | {
29 | method: 'GET',
30 | path: '/data',
31 | handler: 'data.indexData',
32 | config: {
33 | policies: [
34 | {
35 | name: 'admin::hasPermissions',
36 | config: { actions: ['plugin::protected-populate.read'] },
37 | },
38 | ],
39 | },
40 | },
41 | {
42 | method: 'PUT',
43 | path: '/data',
44 | handler: 'data.updateData',
45 | config: {
46 | policies: [
47 | {
48 | name: 'admin::hasPermissions',
49 | config: { actions: ['plugin::protected-populate.read'] },
50 | },
51 | ],
52 | },
53 | },
54 | ];
55 |
--------------------------------------------------------------------------------
/playground/src/api/article/content-types/article/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "collectionType",
3 | "collectionName": "articles",
4 | "info": {
5 | "singularName": "article",
6 | "pluralName": "articles",
7 | "displayName": "Article",
8 | "description": "Create your blog content"
9 | },
10 | "options": {
11 | "draftAndPublish": true
12 | },
13 | "pluginOptions": {},
14 | "attributes": {
15 | "title": {
16 | "type": "string"
17 | },
18 | "description": {
19 | "type": "text",
20 | "maxLength": 80
21 | },
22 | "slug": {
23 | "type": "uid",
24 | "targetField": "title"
25 | },
26 | "cover": {
27 | "type": "media",
28 | "multiple": false,
29 | "required": false,
30 | "allowedTypes": ["images", "files", "videos"]
31 | },
32 | "author": {
33 | "type": "relation",
34 | "relation": "manyToOne",
35 | "target": "api::author.author",
36 | "inversedBy": "articles"
37 | },
38 | "category": {
39 | "type": "relation",
40 | "relation": "manyToOne",
41 | "target": "api::category.category",
42 | "inversedBy": "articles"
43 | },
44 | "blocks": {
45 | "type": "dynamiczone",
46 | "components": ["shared.media", "shared.quote", "shared.rich-text", "shared.slider"]
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/utils/serverRestartWatcher.js:
--------------------------------------------------------------------------------
1 | const SERVER_HAS_NOT_BEEN_KILLED_MESSAGE = 'did-not-kill-server';
2 | const SERVER_HAS_BEEN_KILLED_MESSAGE = 'server is down';
3 |
4 | /**
5 | * Server restart watcher
6 | * Sends an HEAD method to check if the server has been shut down correctly
7 | * and then pings until it's back on
8 | * @param response
9 | * @returns {object} the response data
10 | */
11 | export default function serverRestartWatcher(response, didShutDownServer) {
12 | return new Promise((resolve) => {
13 | fetch(`${strapi.backendURL}/_health`, {
14 | method: 'HEAD',
15 | mode: 'no-cors',
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | 'Keep-Alive': false,
19 | },
20 | })
21 | .then((res) => {
22 | if (res.status >= 400) {
23 | throw new Error(SERVER_HAS_BEEN_KILLED_MESSAGE);
24 | }
25 |
26 | if (!didShutDownServer) {
27 | throw new Error(SERVER_HAS_NOT_BEEN_KILLED_MESSAGE);
28 | }
29 |
30 | resolve(response);
31 | })
32 | .catch((err) => {
33 | setTimeout(() => {
34 | return serverRestartWatcher(
35 | response,
36 | err.message !== SERVER_HAS_NOT_BEEN_KILLED_MESSAGE
37 | ).then(resolve);
38 | }, 100);
39 | });
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/icons/Curve.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { Box } from '@strapi/design-system';
5 |
6 | const StyledBox = styled(Box)`
7 | position: absolute;
8 | left: -1.125rem;
9 | top: 0px;
10 |
11 | &:before {
12 | content: '';
13 | width: ${4 / 16}rem;
14 | height: ${12 / 16}rem;
15 | background: ${({ theme, color }) => theme.colors[color]};
16 | display: block;
17 | }
18 | `;
19 |
20 | const Svg = styled.svg`
21 | position: relative;
22 | flex-shrink: 0;
23 | transform: translate(-0.5px, -1px);
24 |
25 | * {
26 | fill: ${({ theme, color }) => theme.colors[color]};
27 | }
28 | `;
29 |
30 | const Curve = (props) => (
31 |
32 |
40 |
45 |
46 |
47 | );
48 |
49 | Curve.propTypes = {
50 | color: PropTypes.string.isRequired,
51 | };
52 |
53 | export default Curve;
54 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/ComponentList/index.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * ComponentList
4 | *
5 | */
6 | /* eslint-disable import/no-cycle */
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 | import List from '../List';
10 | import Tr from '../Tr';
11 |
12 | function ComponentList({
13 | targetUid,
14 | isFromDynamicZone,
15 | components,
16 | contentTypes,
17 | selectedRows,
18 | updateSelectedRows,
19 | autoReload,
20 | }) {
21 | let typeInfo;
22 | if (targetUid.includes('::')) {
23 | const index = contentTypes.findIndex((data) => data.uid == targetUid);
24 | typeInfo = contentTypes[index];
25 | } else {
26 | const index = components.findIndex((data) => data.uid == targetUid);
27 | typeInfo = components[index];
28 | }
29 | return (
30 |
31 |
32 |
42 |
43 |
44 | );
45 | }
46 |
47 | ComponentList.defaultProps = {
48 | component: null,
49 | customRowComponent: null,
50 | firstLoopComponentUid: null,
51 | isFromDynamicZone: false,
52 | isNestedInDZComponent: false,
53 | };
54 |
55 | ComponentList.propTypes = {
56 | component: PropTypes.string,
57 | customRowComponent: PropTypes.func,
58 | firstLoopComponentUid: PropTypes.string,
59 | isFromDynamicZone: PropTypes.bool,
60 | isNestedInDZComponent: PropTypes.bool,
61 | };
62 |
63 | export default ComponentList;
64 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/RoleAccordion/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box, Accordion, Modal, IconButton } from '@strapi/design-system';
3 | import { Duplicate } from '@strapi/icons';
4 | import List from '../List';
5 | const RoleAccordion = ({
6 | role,
7 | isMain,
8 | items,
9 | targetUid,
10 | contentTypes,
11 | components,
12 | selectedRows,
13 | updateSelectedRows,
14 | autoReload,
15 | handleToggle,
16 | handleSetIsVisible,
17 | expandedID,
18 | }) => {
19 | if (selectedRows[role.type] === null) {
20 | selectedRows[role.type] = {};
21 | selectedRows[role.type]['populate'] = {};
22 | selectedRows[role.type]['fields'] = {};
23 | }
24 | console.log(expandedID);
25 | console.log(role.type);
26 | return (
27 |
28 |
29 | {role.name}
30 |
31 |
32 | handleSetIsVisible(true, role.type)} label="Clone">
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default RoleAccordion;
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ############################
2 | # OS X
3 | ############################
4 |
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 | Icon
9 | .Spotlight-V100
10 | .Trashes
11 | ._*
12 |
13 |
14 | ############################
15 | # Linux
16 | ############################
17 |
18 | *~
19 |
20 |
21 | ############################
22 | # Windows
23 | ############################
24 |
25 | Thumbs.db
26 | ehthumbs.db
27 | Desktop.ini
28 | $RECYCLE.BIN/
29 | *.cab
30 | *.msi
31 | *.msm
32 | *.msp
33 |
34 |
35 | ############################
36 | # Packages
37 | ############################
38 |
39 | *.7z
40 | *.csv
41 | *.dat
42 | *.dmg
43 | *.gz
44 | *.iso
45 | *.jar
46 | *.rar
47 | *.tar
48 | *.zip
49 | *.com
50 | *.class
51 | *.dll
52 | *.exe
53 | *.o
54 | *.seed
55 | *.so
56 | *.swo
57 | *.swp
58 | *.swn
59 | *.swm
60 | *.out
61 | *.pid
62 |
63 |
64 | ############################
65 | # Logs and databases
66 | ############################
67 |
68 | .tmp
69 | *.log
70 | *.sql
71 | *.sqlite
72 | *.sqlite3
73 |
74 |
75 | ############################
76 | # Misc.
77 | ############################
78 |
79 | *#
80 | ssl
81 | .idea
82 | nbproject
83 | public/uploads/*
84 | !public/uploads/.gitkeep
85 | .vscode
86 |
87 | ############################
88 | # Node.js
89 | ############################
90 |
91 | lib-cov
92 | lcov.info
93 | pids
94 | logs
95 | results
96 | node_modules
97 | .node_history
98 | package-lock.json
99 |
100 | ############################
101 | # Tests
102 | ############################
103 |
104 | testApp
105 | coverage
106 |
107 | ############################
108 | # Strapi
109 | ############################
110 |
111 | .env
112 | license.txt
113 | exports
114 | *.cache
115 | dist
116 | build
117 | .strapi-updater.json
118 | /types/*
119 |
120 | ############################
121 | # Custom
122 | ############################
123 | yarn.lock
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/DynamicZoneList/index.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * ComponentList
4 | *
5 | */
6 | /* eslint-disable import/no-cycle */
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 | import List from '../List';
10 | import Tr from '../Tr';
11 | import SelectRender from './SelectRender';
12 | import BoxWrapper from './BoxWrapper';
13 | import { Box } from '@strapi/design-system';
14 | function DynamicZoneList({
15 | item,
16 | targetUid,
17 | isFromDynamicZone,
18 | components,
19 | contentTypes,
20 | selectedRows,
21 | updateSelectedRows,
22 | autoReload,
23 | }) {
24 | let data = [];
25 | data.schema = {};
26 | data.schema.attributes = {};
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 | {item.components.map((component, i) => {
35 | const data = {
36 | type: 'component',
37 | repeatable: false,
38 | component: component,
39 | required: false,
40 | name: component,
41 | };
42 | return (
43 |
53 | );
54 | })}
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
64 | export default DynamicZoneList;
65 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "strapi-plugin-protected-populate",
3 | "version": "2.0.1",
4 | "description": "Protects your populates from the url against bad actors.",
5 | "strapi": {
6 | "name": "protected-populate",
7 | "description": "Protects your populates from the url against bad actors if configured correctly.",
8 | "kind": "plugin",
9 | "displayName": "Protected Populate"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+ssh://git@github.com/strapi-community/strapi-plugin-protected-populate.git"
14 | },
15 | "license": "MIT",
16 | "author": "Boegie19 <34578426+Boegie19@users.noreply.github.com>",
17 | "type": "commonjs",
18 | "exports": {
19 | "./package.json": "./package.json",
20 | "./strapi-admin": {
21 | "source": "./admin/src/index.js",
22 | "import": "./dist/admin/index.mjs",
23 | "require": "./dist/admin/index.js",
24 | "default": "./dist/admin/index.js"
25 | },
26 | "./strapi-server": {
27 | "source": "./server/src/index.js",
28 | "import": "./dist/server/index.mjs",
29 | "require": "./dist/server/index.js",
30 | "default": "./dist/server/index.js"
31 | }
32 | },
33 | "files": [
34 | "dist"
35 | ],
36 | "scripts": {
37 | "build": "strapi-plugin build",
38 | "verify": "strapi-plugin verify",
39 | "watch": "strapi-plugin watch",
40 | "watch:link": "strapi-plugin watch:link"
41 | },
42 | "dependencies": {
43 | "@strapi/design-system": "^2.0.0-rc.11",
44 | "@strapi/icons": "^2.0.0-rc.11",
45 | "react-intl": "^6.7.1"
46 | },
47 | "devDependencies": {
48 | "@strapi/sdk-plugin": "^5.2.6",
49 | "prettier": "^3.3.3",
50 | "react": "^18.3.1",
51 | "react-dom": "^18.3.1",
52 | "react-router-dom": "^6.26.2",
53 | "styled-components": "^6.1.13"
54 | },
55 | "peerDependencies": {
56 | "@strapi/strapi": "^5.0.0",
57 | "react": "^18.3.1",
58 | "react-dom": "^18.3.1",
59 | "react-router-dom": "^6.26.2",
60 | "styled-components": "^6.1.13",
61 | "react-query": "^3.39.3"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/playground/.gitignore:
--------------------------------------------------------------------------------
1 | ############################
2 | # OS X
3 | ############################
4 |
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 | Icon
9 | .Spotlight-V100
10 | .Trashes
11 | ._*
12 |
13 |
14 | ############################
15 | # Linux
16 | ############################
17 |
18 | *~
19 |
20 |
21 | ############################
22 | # Windows
23 | ############################
24 |
25 | Thumbs.db
26 | ehthumbs.db
27 | Desktop.ini
28 | $RECYCLE.BIN/
29 | *.cab
30 | *.msi
31 | *.msm
32 | *.msp
33 |
34 |
35 | ############################
36 | # Packages
37 | ############################
38 |
39 | *.7z
40 | *.csv
41 | *.dat
42 | *.dmg
43 | *.gz
44 | *.iso
45 | *.jar
46 | *.rar
47 | *.tar
48 | *.zip
49 | *.com
50 | *.class
51 | *.dll
52 | *.exe
53 | *.o
54 | *.seed
55 | *.so
56 | *.swo
57 | *.swp
58 | *.swn
59 | *.swm
60 | *.out
61 | *.pid
62 |
63 |
64 | ############################
65 | # Logs and databases
66 | ############################
67 |
68 | .tmp
69 | *.log
70 | *.sql
71 | *.sqlite
72 | *.sqlite3
73 |
74 |
75 | ############################
76 | # Misc.
77 | ############################
78 |
79 | *#
80 | ssl
81 | .idea
82 | nbproject
83 | public/uploads/*
84 | !public/uploads/.gitkeep
85 | .tsbuildinfo
86 | .eslintcache
87 |
88 | ############################
89 | # Node.js
90 | ############################
91 |
92 | lib-cov
93 | lcov.info
94 | pids
95 | logs
96 | results
97 | node_modules
98 | .node_history
99 |
100 | ############################
101 | # Package managers
102 | ############################
103 |
104 | .yarn/*
105 | !.yarn/cache
106 | !.yarn/unplugged
107 | !.yarn/patches
108 | !.yarn/releases
109 | !.yarn/sdks
110 | !.yarn/versions
111 | .pnp.*
112 | yarn-error.log
113 |
114 | ############################
115 | # Tests
116 | ############################
117 |
118 | coverage
119 |
120 | ############################
121 | # Strapi
122 | ############################
123 |
124 | .env
125 | license.txt
126 | exports
127 | .strapi
128 | dist
129 | build
130 | .strapi-updater.json
131 | .strapi-cloud.json
132 |
133 | types/*
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/DynamicZoneList/SelectRender.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * List
4 | *
5 | */
6 |
7 | /* eslint-disable import/no-cycle */
8 | import React, { useState } from 'react';
9 | import ComponentList from '../ComponentList';
10 | import ListRow from '../ListRow';
11 |
12 | /* eslint-disable jsx-a11y/click-events-have-key-events */
13 | /* eslint-disable jsx-a11y/no-static-element-interactions */
14 |
15 | function SelectRender({
16 | isMain,
17 | item,
18 | contentTypes,
19 | components,
20 | selectedRows,
21 | updateSelectedRows,
22 | autoReload,
23 | }) {
24 | const { type, name } = item;
25 | let value =
26 | typeof selectedRows.on !== 'undefined' && typeof selectedRows.on[name] !== 'undefined';
27 |
28 | const setValue = (value) => {
29 | if (value == true) {
30 | if (typeof selectedRows.on == 'undefined') {
31 | selectedRows.on = {};
32 | }
33 | selectedRows.on[name] = {};
34 | updateSelectedRows();
35 | } else if (
36 | typeof selectedRows.on !== 'undefined' &&
37 | typeof selectedRows.on[name] !== 'undefined'
38 | ) {
39 | delete selectedRows.on[name];
40 | updateSelectedRows();
41 | }
42 | };
43 | if (value === false) {
44 | return (
45 |
46 |
53 |
54 | );
55 | }
56 | return (
57 |
58 |
65 |
66 | {type === 'component' && (
67 |
76 | )}
77 |
78 | );
79 | }
80 |
81 | export default SelectRender;
82 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | ############################
4 | # OS X
5 | ############################
6 |
7 | .DS_Store
8 | .AppleDouble
9 | .LSOverride
10 | Icon
11 | .Spotlight-V100
12 | .Trashes
13 | ._*
14 |
15 |
16 | ############################
17 | # Linux
18 | ############################
19 |
20 | *~
21 |
22 |
23 | ############################
24 | # Windows
25 | ############################
26 |
27 | Thumbs.db
28 | ehthumbs.db
29 | Desktop.ini
30 | $RECYCLE.BIN/
31 | *.cab
32 | *.msi
33 | *.msm
34 | *.msp
35 |
36 |
37 | ############################
38 | # Packages
39 | ############################
40 |
41 | *.7z
42 | *.csv
43 | *.dat
44 | *.dmg
45 | *.gz
46 | *.iso
47 | *.jar
48 | *.rar
49 | *.tar
50 | *.zip
51 | *.com
52 | *.class
53 | *.dll
54 | *.exe
55 | *.o
56 | *.seed
57 | *.so
58 | *.swo
59 | *.swp
60 | *.swn
61 | *.swm
62 | *.out
63 | *.pid
64 |
65 |
66 | ############################
67 | # Logs and databases
68 | ############################
69 |
70 | .tmp
71 | *.log
72 | *.sql
73 | *.sqlite
74 | *.sqlite3
75 |
76 |
77 | ############################
78 | # Misc.
79 | ############################
80 |
81 | *#
82 | ssl
83 | .idea
84 | nbproject
85 | .tsbuildinfo
86 | .eslintcache
87 | .env
88 |
89 |
90 | ############################
91 | # Strapi
92 | ############################
93 |
94 | public/uploads/*
95 | !public/uploads/.gitkeep
96 |
97 |
98 | ############################
99 | # Build
100 | ############################
101 |
102 | dist
103 | build
104 |
105 |
106 | ############################
107 | # Node.js
108 | ############################
109 |
110 | lib-cov
111 | lcov.info
112 | pids
113 | logs
114 | results
115 | node_modules
116 | .node_history
117 |
118 |
119 | ############################
120 | # Package managers
121 | ############################
122 |
123 | .yarn/*
124 | !.yarn/cache
125 | !.yarn/unplugged
126 | !.yarn/patches
127 | !.yarn/releases
128 | !.yarn/sdks
129 | !.yarn/versions
130 | .pnp.*
131 | yarn-error.log
132 |
133 |
134 | ############################
135 | # Tests
136 | ############################
137 |
138 | coverage
139 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/ListRow/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Checkbox , Flex } from '@strapi/design-system';
3 | import { Typography } from '@strapi/design-system';
4 | import Curve from '../../icons/Curve';
5 | import BoxWrapper from './BoxWrapper';
6 | import AttributeIcon from '../AttributeIcon';
7 | import DisplayedType from './DisplayedType';
8 |
9 | function ListRow({
10 | customField,
11 | firstLoopComponentUid,
12 | isFromDynamicZone,
13 | name,
14 | relation,
15 | repeatable,
16 | secondLoopComponentUid,
17 | target,
18 | targetUid,
19 | type,
20 | isMain,
21 | value,
22 | setValue,
23 | displayName,
24 | autoReload,
25 | }) {
26 | const isMorph = type === 'relation' && relation.includes('morph');
27 | const ico = ['integer', 'biginteger', 'float', 'decimal'].includes(type) ? 'number' : type;
28 |
29 | const src = target ? 'relation' : ico;
30 |
31 | let loopNumber;
32 | if (secondLoopComponentUid && firstLoopComponentUid) {
33 | loopNumber = 2;
34 | } else if (firstLoopComponentUid) {
35 | loopNumber = 1;
36 | } else {
37 | loopNumber = 0;
38 | }
39 |
40 | return (
41 |
42 |
43 | {isMain !== true && }
44 |
45 |
46 | {name}
47 |
48 |
49 |
50 | {target ? (
51 |
52 | Relation with
53 | {target}
54 |
55 | ) : (
56 |
57 | )}
58 |
59 |
60 |
61 | setValue(val)}
65 | checked={value}
66 | />
67 |
68 |
69 |
70 | );
71 | }
72 | export default memo(ListRow);
73 | export { ListRow };
74 |
--------------------------------------------------------------------------------
/playground/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 Getting started with Strapi
2 |
3 | Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/dev-docs/cli) (CLI) which lets you scaffold and manage your project in seconds.
4 |
5 | ### `develop`
6 |
7 | Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-develop)
8 |
9 | ```
10 | npm run develop
11 | # or
12 | yarn develop
13 | ```
14 |
15 | ### `start`
16 |
17 | Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-start)
18 |
19 | ```
20 | npm run start
21 | # or
22 | yarn start
23 | ```
24 |
25 | ### `build`
26 |
27 | Build your admin panel. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-build)
28 |
29 | ```
30 | npm run build
31 | # or
32 | yarn build
33 | ```
34 |
35 | ## ⚙️ Deployment
36 |
37 | Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
38 |
39 | ```
40 | yarn strapi deploy
41 | ```
42 |
43 | ## 📚 Learn more
44 |
45 | - [Resource center](https://strapi.io/resource-center) - Strapi resource center.
46 | - [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation.
47 | - [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community.
48 | - [Strapi blog](https://strapi.io/blog) - Official Strapi blog containing articles made by the Strapi team and the community.
49 | - [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements.
50 |
51 | Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome!
52 |
53 | ## ✨ Community
54 |
55 | - [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team.
56 | - [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members.
57 | - [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi.
58 |
59 | ---
60 |
61 | 🤫 Psst! [Strapi is hiring](https://strapi.io/careers).
62 |
--------------------------------------------------------------------------------
/playground/config/database.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | export default ({ env }) => {
4 | const client = env('DATABASE_CLIENT', 'sqlite');
5 |
6 | const connections = {
7 | mysql: {
8 | connection: {
9 | host: env('DATABASE_HOST', 'localhost'),
10 | port: env.int('DATABASE_PORT', 3306),
11 | database: env('DATABASE_NAME', 'strapi'),
12 | user: env('DATABASE_USERNAME', 'strapi'),
13 | password: env('DATABASE_PASSWORD', 'strapi'),
14 | ssl: env.bool('DATABASE_SSL', false) && {
15 | key: env('DATABASE_SSL_KEY', undefined),
16 | cert: env('DATABASE_SSL_CERT', undefined),
17 | ca: env('DATABASE_SSL_CA', undefined),
18 | capath: env('DATABASE_SSL_CAPATH', undefined),
19 | cipher: env('DATABASE_SSL_CIPHER', undefined),
20 | rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
21 | },
22 | },
23 | pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
24 | },
25 | postgres: {
26 | connection: {
27 | connectionString: env('DATABASE_URL'),
28 | host: env('DATABASE_HOST', 'localhost'),
29 | port: env.int('DATABASE_PORT', 5432),
30 | database: env('DATABASE_NAME', 'strapi'),
31 | user: env('DATABASE_USERNAME', 'strapi'),
32 | password: env('DATABASE_PASSWORD', 'strapi'),
33 | ssl: env.bool('DATABASE_SSL', false) && {
34 | key: env('DATABASE_SSL_KEY', undefined),
35 | cert: env('DATABASE_SSL_CERT', undefined),
36 | ca: env('DATABASE_SSL_CA', undefined),
37 | capath: env('DATABASE_SSL_CAPATH', undefined),
38 | cipher: env('DATABASE_SSL_CIPHER', undefined),
39 | rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
40 | },
41 | schema: env('DATABASE_SCHEMA', 'public'),
42 | },
43 | pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
44 | },
45 | sqlite: {
46 | connection: {
47 | filename: path.join(__dirname, '..', '..', env('DATABASE_FILENAME', '.tmp/data.db')),
48 | },
49 | useNullAsDefault: true,
50 | },
51 | };
52 |
53 | return {
54 | connection: {
55 | client,
56 | ...connections[client],
57 | acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000),
58 | },
59 | };
60 | };
61 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/AttributeIcon/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import PropTypes from 'prop-types';
4 | import { Box } from '@strapi/design-system';
5 | import {
6 | ComponentField,
7 | CollectionType,
8 | DateField,
9 | BooleanField,
10 | DynamicZoneField,
11 | EmailField,
12 | EnumerationField,
13 | JsonField,
14 | MediaField,
15 | PasswordField,
16 | RelationField,
17 | SingleType,
18 | TextField,
19 | UidField,
20 | NumberField
21 | } from '@strapi/icons/symbols';
22 | import { useStrapiApp } from '@strapi/admin/strapi-admin';
23 | // TODO ADD BLOCKS field
24 | const iconByTypes = {
25 | biginteger: NumberField,
26 | boolean: BooleanField,
27 | collectionType: CollectionType,
28 | component: ComponentField,
29 | contentType: CollectionType,
30 | date: DateField,
31 | datetime: DateField,
32 | decimal: NumberField,
33 | dynamiczone: DynamicZoneField,
34 | email: EmailField,
35 | enum: EnumerationField,
36 | enumeration: EnumerationField,
37 | file: MediaField,
38 | files: MediaField,
39 | float: NumberField,
40 | integer: NumberField,
41 | json: JsonField,
42 | JSON: JsonField,
43 | blocks: JsonField,
44 | media: MediaField,
45 | number: NumberField,
46 | password: PasswordField,
47 | relation: RelationField,
48 | richtext: TextField,
49 | singleType: SingleType,
50 | string: TextField,
51 | text: TextField,
52 | time: DateField,
53 | timestamp: DateField,
54 | uid: UidField,
55 | id: UidField,
56 | };
57 |
58 | const IconBox = styled(Box)`
59 | width: ${32 / 16};
60 | height: ${24 / 16};
61 | box-sizing: content-box;
62 | `;
63 |
64 | const AttributeIcon = ({ type, customField, ...rest }) => {
65 | //const customFieldsRegistry = useStrapiApp('ProtectedPopulate', (state) => state.customFields);
66 |
67 | let Compo = iconByTypes[type];
68 |
69 | if (customField) {
70 | const { icon } = customFieldsRegistry.get(customField);
71 |
72 | if (icon) {
73 | Compo = icon;
74 | }
75 | }
76 |
77 | if (!iconByTypes[type]) {
78 | return null;
79 | }
80 |
81 | return ;
82 | };
83 |
84 | AttributeIcon.defaultProps = {
85 | customField: null,
86 | };
87 |
88 | AttributeIcon.propTypes = {
89 | type: PropTypes.string.isRequired,
90 | customField: PropTypes.string,
91 | };
92 |
93 | export default AttributeIcon;
94 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/List/index.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * List
4 | *
5 | */
6 |
7 | /* eslint-disable import/no-cycle */
8 | import React from 'react';
9 | import { Box, Typography } from '@strapi/design-system';
10 | import SelectRender from '../SelectRender';
11 | import BoxWrapper from './BoxWrapper';
12 |
13 | /* eslint-disable jsx-a11y/click-events-have-key-events */
14 | /* eslint-disable jsx-a11y/no-static-element-interactions */
15 |
16 | function List({
17 | isMain,
18 | items,
19 | targetUid,
20 | contentTypes,
21 | components,
22 | selectedRows,
23 | updateSelectedRows,
24 | autoReload,
25 | }) {
26 | if (targetUid.includes('::')) {
27 | items.schema.attributes.createdAt = {
28 | type: 'datetime',
29 | };
30 | items.schema.attributes.updatedAt = {
31 | type: 'datetime',
32 | };
33 | items.schema.attributes.id = {
34 | type: 'id',
35 | };
36 | items.schema.attributes.documentId = {
37 | type: 'id',
38 | };
39 | }
40 | if (items.schema.populateCreatorFields) {
41 | (items.schema.attributes.createdBy = {
42 | type: 'relation',
43 | relation: 'oneToMany',
44 | target: 'admin::user',
45 | }),
46 | items.schema.attributes.updatedBy = {
47 | type: 'relation',
48 | relation: 'oneToMany',
49 | target: 'admin::user',
50 | };
51 | }
52 | if (items.schema.draftAndPublish) {
53 | items.schema.attributes.publishedAt = {
54 | type: 'datetime',
55 | };
56 | }
57 | if (items.schema?.pluginOptions?.i18n?.localized === true) {
58 | items.schema.attributes.localizations = {
59 | type: 'relation',
60 | relation: 'oneToMany',
61 | target: items.uid,
62 | };
63 | items.schema.attributes.locale = {
64 | type: 'text',
65 | };
66 | }
67 | return (
68 |
69 |
74 |
75 | {isMain && (
76 |
77 |
78 |
79 |
80 | Name
81 |
82 |
83 |
84 |
85 | Type
86 |
87 |
88 |
89 |
90 | )}
91 |
92 | {Object.entries(items.schema.attributes).map(([key, item]) => {
93 | item.name = key;
94 | item.plugin;
95 | return (
96 |
106 | );
107 | })}
108 |
109 |
110 |
111 |
112 | );
113 | }
114 |
115 | export default List;
116 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
16 |
17 | ## Table of Contents
18 |
19 | - [🚦 Current Status](#current-status)
20 | - [✨ Features](#features)
21 | - [🤔 Motivation](#motivation)
22 | - [🖐 Requirements](#requirements)
23 | - [⏳ Installation](#installation)
24 | - [🔧 Configuration](#configuration)
25 | - [Contributing](#contributing)
26 | - [Migration](#migration)
27 | - [License](#license)
28 |
29 | ## 🚦 Current Status
30 | For more information on contributing please see [the contrib message below](#contributing).
31 |
32 | ## ✨ Features
33 |
34 | These are the primary features that are finished or currently being worked on:
35 |
36 | - [x] Protected your Get request populates and fields
37 | - [x] Allow you to protect routes per role
38 |
39 | ## 🤔 Motivation
40 |
41 | The purpose of this plugin is to have a easy way to protect your get endpoints from getting to much information out of them.
42 | I made this plugin since I got sick and tired of writing complex policies to do this exact thing.
43 |
44 | ## 🖐 Requirements
45 |
46 | Supported Strapi Versions:
47 | Plugin V2
48 | | Strapi Version | Supported | Tested On |
49 | | -------------- | --------- | ------------- |
50 | | v4 | ❌ | N/A |
51 | | v5 | ✅ | April 2023 |
52 |
53 | Plutin V1
54 | | Strapi Version | Supported | Tested On |
55 | | -------------- | --------- | ------------- |
56 | | v3 | ❌ | N/A |
57 | | <=v4.5.2 | ❌ | N/A |
58 | | v4.5.3/4.6.2 | ✅ | December 2022 |
59 | | v4.7.0/4.7.1 | ❌ | N/A |
60 | | v4.8.0+ | ✅ | April 2023 |
61 | | v5 | ❌ | April 2023 |
62 |
63 | **This plugin will not work on any version older then v4.5.3 since I am using the on syntax for dynamic zones wat was added in that version**
64 |
65 | ## ⏳ Installation
66 |
67 | Install the plugin in your Strapi project or your Strapi plugin.
68 |
69 | ```bash
70 | # Using Yarn (Recommended)
71 | yarn add strapi-plugin-protected-populate
72 |
73 | # Using npm
74 | npm install strapi-plugin-protected-populate
75 | ```
76 |
77 | ## 🔧 Configuration
78 |
79 | WIP
80 |
81 | ### Config
82 |
83 | standard config
84 | add the following config to your config/plugins.js
85 | ```js
86 | module.exports = () => {
87 | return {
88 | 'protected-populate': {
89 | enabled: true,
90 | },
91 | };
92 | };
93 | ```
94 |
95 | enable auto populate will automatically populate all fields and populates if no ctx.query.populate / ctx.query.fields is found.
96 |
97 | add the following config to your config/plugins.js
98 | ```js
99 | module.exports = () => {
100 | return {
101 | 'protected-populate': {
102 | enabled: true,
103 | config: {
104 | ['auto-populate']: true,
105 | },
106 | },
107 | };
108 | };
109 | ```
110 |
111 | ## Contributing
112 |
113 | Please open issues before making a pull request so that we can talk about what you want to change for the best results.
114 |
115 | ## Migration
116 |
117 | V1.0.0 to v1.1.0
118 | choose what way you want to do the migration GUI or File change
119 |
120 | ### GUI way
121 |
122 | Go to the gui select all Media types and deselect them
123 | Update to v1.1.1 select all deselected Media
124 |
125 | ### File change
126 |
127 | Find all the media in fields and change them to populate
128 |
129 | ## License
130 |
131 | MIT
132 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/README.md:
--------------------------------------------------------------------------------
1 |
16 |
17 | ## Table of Contents
18 |
19 | - [🚦 Current Status](#current-status)
20 | - [✨ Features](#features)
21 | - [🤔 Motivation](#motivation)
22 | - [🖐 Requirements](#requirements)
23 | - [⏳ Installation](#installation)
24 | - [🔧 Configuration](#configuration)
25 | - [Contributing](#contributing)
26 | - [Migration](#migration)
27 | - [License](#license)
28 |
29 | ## 🚦 Current Status
30 | For more information on contributing please see [the contrib message below](#contributing).
31 |
32 | ## ✨ Features
33 |
34 | These are the primary features that are finished or currently being worked on:
35 |
36 | - [x] Protected your Get request populates and fields
37 | - [x] Allow you to protect routes per role
38 |
39 | ## 🤔 Motivation
40 |
41 | The purpose of this plugin is to have a easy way to protect your get endpoints from getting to much information out of them.
42 | I made this plugin since I got sick and tired of writing complex policies to do this exact thing.
43 |
44 | ## 🖐 Requirements
45 |
46 | Supported Strapi Versions:
47 | Plugin V2
48 | | Strapi Version | Supported | Tested On |
49 | | -------------- | --------- | ------------- |
50 | | v4 | ❌ | N/A |
51 | | v5 | ✅ | April 2023 |
52 |
53 | Plutin V1
54 | | Strapi Version | Supported | Tested On |
55 | | -------------- | --------- | ------------- |
56 | | v3 | ❌ | N/A |
57 | | <=v4.5.2 | ❌ | N/A |
58 | | v4.5.3/4.6.2 | ✅ | December 2022 |
59 | | v4.7.0/4.7.1 | ❌ | N/A |
60 | | v4.8.0+ | ✅ | April 2023 |
61 | | v5 | ❌ | April 2023 |
62 |
63 | **This plugin will not work on any version older then v4.5.3 since I am using the on syntax for dynamic zones wat was added in that version**
64 |
65 | ## ⏳ Installation
66 |
67 | Install the plugin in your Strapi project or your Strapi plugin.
68 |
69 | ```bash
70 | # Using Yarn (Recommended)
71 | yarn add strapi-plugin-protected-populate
72 |
73 | # Using npm
74 | npm install strapi-plugin-protected-populate
75 | ```
76 |
77 | ## 🔧 Configuration
78 |
79 | WIP
80 |
81 | ### Config
82 |
83 | standard config
84 | add the following config to your config/plugins.js
85 | ```js
86 | module.exports = () => {
87 | return {
88 | 'protected-populate': {
89 | enabled: true,
90 | },
91 | };
92 | };
93 | ```
94 |
95 | enable auto populate will automatically populate all fields and populates if no ctx.query.populate / ctx.query.fields is found.
96 |
97 | add the following config to your config/plugins.js
98 | ```js
99 | module.exports = () => {
100 | return {
101 | 'protected-populate': {
102 | enabled: true,
103 | config: {
104 | ['auto-populate']: true,
105 | },
106 | },
107 | };
108 | };
109 | ```
110 |
111 | ## Contributing
112 |
113 | Please open issues before making a pull request so that we can talk about what you want to change for the best results.
114 |
115 | ## Migration
116 |
117 | V1.0.0 to v1.1.0
118 | choose what way you want to do the migration GUI or File change
119 |
120 | ### GUI way
121 |
122 | Go to the gui select all Media types and deselect them
123 | Update to v1.1.1 select all deselected Media
124 |
125 | ### File change
126 |
127 | Find all the media in fields and change them to populate
128 |
129 | ## License
130 |
131 | MIT
132 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/register.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | module.exports = ({ strapi }) => {
5 | if (fs.existsSync(strapi.dirs.app.src + '/protected-populate/index.json')) {
6 | const fileData = fs.readFileSync(strapi.dirs.app.src + `/protected-populate/index.json`, {
7 | encoding: 'utf8',
8 | });
9 | strapi.plugin('protected-populate').protectedRoutes = JSON.parse(fileData);
10 | } else {
11 | strapi.plugin('protected-populate').protectedRoutes = {};
12 | }
13 |
14 | const savedPluginRoutes = {};
15 |
16 | const savePluginRoutesWithoutPrefix = (value, path, method, data) => {
17 | value.forEach((route) => {
18 | if (typeof route.config !== 'undefined' && route.config.prefix === '') {
19 | savedPluginRoutes[route.method + ' ' + route.path] = data;
20 | }
21 | });
22 | };
23 |
24 | const insertMiddleware = (value, path, method, data) => {
25 | value.forEach((route) => {
26 | if (route.path == path && method == route.method) {
27 | if (typeof route.config === 'undefined') {
28 | route.config = {};
29 | }
30 | route.config['protected-populate'] = data;
31 | if (typeof route.config.middlewares === 'undefined') {
32 | route.config.middlewares = ['plugin::protected-populate.protect'];
33 | } else {
34 | route.config.middlewares.push('plugin::protected-populate.protect');
35 | }
36 | return true;
37 | }
38 | });
39 | };
40 |
41 | const insertMiddlewareByPath = (routesList, path, method, data, func) => {
42 | let routes = routesList;
43 | while (routes.length !== 0) {
44 | if (
45 | (typeof routes[0] === 'object' && Array.isArray(routes[0])) ||
46 | (routes[0].routes === 'object' && Array.isArray(routes[0].routes))
47 | ) {
48 | if (typeof routes[0] === 'object' && Array.isArray(routes[0])) {
49 | if (func(routes[0], path, method, data)) {
50 | return true;
51 | }
52 | } else if (routes[0].routes === 'object' && Array.isArray(routes[0].routes)) {
53 | if (func(routes[0], path, method, data)) {
54 | return true;
55 | }
56 | }
57 | routes.splice(0, 1);
58 | } else {
59 | for (const [key, value] of Object.entries(routes[0])) {
60 | if (value.type !== 'admin' && (key === 'routes' || key === 'content-api')) {
61 | routes.push(value);
62 | }
63 | }
64 | routes.splice(0, 1);
65 | }
66 | }
67 | return false;
68 | };
69 |
70 | for (const pluginName of Object.keys(strapi.plugins)) {
71 | insertMiddlewareByPath(
72 | [strapi.plugins[pluginName].routes],
73 | '',
74 | '',
75 | pluginName,
76 | savePluginRoutesWithoutPrefix
77 | );
78 | }
79 |
80 | for (const [path, data] of Object.entries(strapi.plugin('protected-populate').protectedRoutes)) {
81 | const pathPluginSplit = path.split('/');
82 | const method = pathPluginSplit[0].trim();
83 | let pluginName = pathPluginSplit[2];
84 | if (pathPluginSplit[1] === 'api' && typeof strapi.plugins[pluginName] !== 'undefined') {
85 | pathPluginSplit.splice(0, 3);
86 | const pluginPath = '/' + pathPluginSplit.join('/');
87 | if (
88 | insertMiddlewareByPath(
89 | [strapi.plugins[pluginName].routes],
90 | pluginPath,
91 | method,
92 | data,
93 | insertMiddleware
94 | )
95 | ) {
96 | continue;
97 | }
98 | } else {
99 | pathPluginSplit.splice(0, 2);
100 | const pluginPath = '/' + pathPluginSplit.join('/');
101 | pluginName = savedPluginRoutes[method + ' ' + pluginPath];
102 | if (typeof pluginName !== 'undefined') {
103 | if (
104 | insertMiddlewareByPath(
105 | [strapi.plugins[pluginName].routes],
106 | pluginPath,
107 | method,
108 | data,
109 | insertMiddleware
110 | )
111 | ) {
112 | continue;
113 | }
114 | }
115 | }
116 | const pathApiSplit = path.split('/');
117 | pathApiSplit.splice(0, 2);
118 | const pathApi = '/' + pathApiSplit.join('/');
119 | let routes = [];
120 | for (const [_, api] of Object.entries(strapi.apis)) {
121 | for (const [_, route] of Object.entries(api.routes)) {
122 | routes.push(route.routes);
123 | }
124 | }
125 | if (insertMiddlewareByPath(routes, pathApi, method, data, insertMiddleware)) {
126 | continue;
127 | }
128 | }
129 | };
130 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/SelectRender/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import ComponentList from '../ComponentList';
3 | import ListRow from '../ListRow';
4 | import DynamicZoneList from '../DynamicZoneList';
5 |
6 |
7 | function SelectRender({
8 | isMain,
9 | item,
10 | contentTypes,
11 | components,
12 | selectedRows,
13 | updateSelectedRows,
14 | autoReload,
15 | }) {
16 | let value;
17 | const { type, name } = item;
18 | if (['component', 'relation', 'dynamiczone', 'media'].includes(type)) {
19 | value =
20 | typeof selectedRows.populate !== 'undefined' &&
21 | typeof selectedRows.populate[name] !== 'undefined';
22 | } else {
23 | value =
24 | typeof selectedRows.fields !== 'undefined' &&
25 | selectedRows.fields.findIndex((field) => field === name) !== -1;
26 | }
27 | const setValue = (value) => {
28 | if (['component', 'relation', 'dynamiczone', 'media'].includes(type)) {
29 | if (value == true) {
30 | if (typeof selectedRows.populate == 'undefined') {
31 | selectedRows.populate = {};
32 | }
33 | selectedRows.populate[name] = {};
34 | if (type === 'dynamiczone') {
35 | selectedRows.populate[name]['on'] = {};
36 | }
37 | updateSelectedRows();
38 | } else if (
39 | typeof selectedRows.populate !== 'undefined' &&
40 | typeof selectedRows.populate[name] !== 'undefined'
41 | ) {
42 | delete selectedRows.populate[name];
43 | if (selectedRows.populate.length === 0) {
44 | delete selectedRows.populate;
45 | }
46 | updateSelectedRows();
47 | }
48 | } else {
49 | if (value == true) {
50 | if (typeof selectedRows.fields == 'undefined') {
51 | selectedRows.fields = [];
52 | }
53 | selectedRows.fields.push(name);
54 | updateSelectedRows();
55 | } else if (
56 | typeof selectedRows.fields !== 'undefined' &&
57 | selectedRows.fields.findIndex((field) => field === name) !== -1
58 | ) {
59 | var index = selectedRows.fields.indexOf(name);
60 | if (index !== -1) {
61 | selectedRows.fields.splice(index, 1);
62 | }
63 | if (selectedRows.fields.length === 0) {
64 | delete selectedRows.fields;
65 | }
66 | updateSelectedRows();
67 | }
68 | }
69 | };
70 | if (type === 'relation' && typeof item.target !== 'string') {
71 | return <>>;
72 | }
73 |
74 | if (value === false) {
75 | return (
76 |
77 |
84 |
85 | );
86 | }
87 | return (
88 |
89 |
96 |
97 | {type === 'component' && (
98 |
107 | )}
108 | {type === 'relation' && (
109 |
118 | )}
119 | {type === 'media' && (
120 |
129 | )}
130 |
131 | {type === 'dynamiczone' && (
132 |
140 | )}
141 |
142 | );
143 | }
144 |
145 | export default SelectRender;
146 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/server/src/middlewares/helpers/protect-route.js:
--------------------------------------------------------------------------------
1 | const { parseType } = require('@strapi/utils');
2 |
3 | /*
4 | Rules
5 | 1 in dynamicZones if on is found populate and fields get ignored
6 | 2 populating * populates 1 level + 1 level of dynamic zones
7 | 3 fields and populate in dynamic zones get replicated to all its children example:
8 | fields=["field"] If A and B both have the field field and c does not then field will be populated on a and b
9 | 4 if populate[name] = true then load 1 level deep + 1 level of dynamic zones
10 |
11 | */
12 |
13 | const protectPopulate = (populate, info) => {
14 | let mainBoolean;
15 | try {
16 | mainBoolean = parseType('boolean', populate);
17 | } catch (error) {
18 | //Is not a boolean
19 | }
20 | if (mainBoolean === true || mainBoolean === false) {
21 | populate = {};
22 | }
23 | //fields
24 | if (typeof info.on === 'undefined') {
25 | if (typeof info.fields === 'undefined') {
26 | populate.fields = ['id'];
27 | } else if (typeof populate.fields === 'undefined') {
28 | //Don't have to check main mainBoolean since if it is true fields is also "undefined"
29 | populate.fields = ['id', ...info.fields];
30 | } else if (Array.isArray(populate.fields)) {
31 | let fields = ['id'];
32 | populate.fields.forEach((value) => {
33 | if (info.fields.includes(value)) {
34 | fields.push(value);
35 | }
36 | });
37 | populate.fields = fields;
38 | } else {
39 | //Unexpected input handle it if it was empty
40 | populate.fields = ['id', ...info.fields];
41 | }
42 | }
43 | //Deal with array input for populate
44 | if (Array.isArray(populate.populate)) {
45 | const data = {};
46 | populate.populate.forEach((key) => {
47 | let data2 = data;
48 | const list = key.split('.');
49 | list.forEach((key) => {
50 | if (typeof data2.populate === 'undefined') {
51 | data2.populate = {};
52 | }
53 | if (typeof data2.populate[key] === 'undefined') {
54 | data2.populate[key] = {};
55 | }
56 | data2 = data2.populate[key];
57 | });
58 | });
59 | populate.populate = data.populate;
60 | }
61 |
62 | //on and populate
63 | if (typeof info.on !== 'undefined') {
64 | //dynamicZone
65 | if (typeof populate.on !== 'undefined') {
66 | //on
67 | if (Object.keys(info.on).length === 0) {
68 | populate = { on: {} };
69 | } else if (mainBoolean == true) {
70 | let populateAllowed = {};
71 | for (const key of Object.keys(info.on)) {
72 | let populateData = protectPopulate({}, info.on[key]);
73 | populateAllowed[key] = populateData;
74 | }
75 | populate.on = populateAllowed;
76 | } else if (typeof populate.on !== 'undefined') {
77 | let populateAllowed = {};
78 | for (const [key, _] of Object.entries(populate.on)) {
79 | if (key in info.on) {
80 | let data = protectPopulate(populate.on[key], info.on[key]);
81 | populateAllowed[key] = data;
82 | }
83 | }
84 | populate.on = populateAllowed;
85 | }
86 | } else if (typeof populate.populate !== 'undefined' || Array.isArray(populate.fields)) {
87 | //populate
88 | const AllowedList = {};
89 | AllowedList['on'] = {};
90 | Object.keys(info.on).forEach((key) => {
91 | let data = protectPopulate(JSON.parse(JSON.stringify(populate)), info.on[key]);
92 | AllowedList['on'][key] = data;
93 | });
94 | populate = AllowedList;
95 | } else {
96 | //empty
97 | let AllowedList = {};
98 | AllowedList['on'] = {};
99 | Object.keys(info.on).forEach((key) => {
100 | let data = protectPopulate({}, info.on[key]);
101 | AllowedList['on'][key] = data;
102 | });
103 | delete populate['fields'];
104 | delete populate['populate'];
105 | populate = AllowedList;
106 | }
107 | } else {
108 | //DZ
109 | //populate
110 | if (typeof info.populate === 'undefined') {
111 | populate.populate = {};
112 | } else if (populate.populate === '*' || mainBoolean === true) {
113 | let populateAllowed = {};
114 | for (const key of Object.keys(info.populate)) {
115 | let data = protectPopulate({}, info.populate[key]);
116 | populateAllowed[key] = data;
117 | }
118 | populate.populate = populateAllowed;
119 | } else if (typeof populate.populate !== 'undefined') {
120 | let populateAllowed = {};
121 | for (const [key, _] of Object.entries(populate.populate)) {
122 | if (key in info.populate) {
123 | let data = protectPopulate(populate.populate[key], info.populate[key]);
124 | populateAllowed[key] = data;
125 | }
126 | }
127 | populate.populate = populateAllowed;
128 | }
129 | }
130 | return populate;
131 | };
132 |
133 | const joinsFiltersArray = ['$or', '$and'];
134 | const joinsFiltersObject = ['$not'];
135 | const protectFilters = (filters, info) => {
136 | if (filters === undefined) {
137 | return undefined;
138 | }
139 | let filtersAllowed = {};
140 | for (const key of Object.keys(filters)) {
141 | //TODO check if joinsFilters are cease sensative
142 | if (joinsFiltersArray.includes(key)) {
143 | if (Array.isArray(filters[key])) {
144 | filtersAllowed[key] = [];
145 | for (const object of filters[key]) {
146 | if (typeof object === 'object') {
147 | filtersAllowed[key].push(protectFilters(object, info));
148 | }
149 | }
150 | }
151 | } else if (joinsFiltersObject.includes(key)) {
152 | for (const object of Object.keys(filters[key])) {
153 | if (typeof object === 'object') {
154 | filtersAllowed[key] = protectFilters(object, info);
155 | }
156 | }
157 | } else if (Array.isArray(info.fields) && info.fields.includes(key)) {
158 | filtersAllowed[key] = filters[key];
159 | } else if (info.populate !== undefined && info.populate[key] !== undefined) {
160 | filtersAllowed[key] = protectFilters(filters[key], info.populate[key]);
161 | }
162 | }
163 | return filtersAllowed;
164 | };
165 |
166 | const protectRoute = (query, info) => {
167 | const savePopulate = protectPopulate(query, info);
168 | query.populate = savePopulate.populate || savePopulate.on;
169 | query.fields = savePopulate.fields;
170 | query.filters = protectFilters(query.filters, info);
171 | return query;
172 | };
173 |
174 | module.exports = {
175 | protectPopulate,
176 | protectFilters,
177 | protectRoute,
178 | };
179 |
--------------------------------------------------------------------------------
/playground/scripts/seed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs-extra');
4 | const path = require('path');
5 | const mime = require('mime-types');
6 | const { categories, authors, articles, global, about } = require('../data/data.json');
7 |
8 | async function seedExampleApp() {
9 | const shouldImportSeedData = await isFirstRun();
10 |
11 | if (shouldImportSeedData) {
12 | try {
13 | console.log('Setting up the template...');
14 | await importSeedData();
15 | console.log('Ready to go');
16 | } catch (error) {
17 | console.log('Could not import seed data');
18 | console.error(error);
19 | }
20 | } else {
21 | console.log(
22 | 'Seed data has already been imported. We cannot reimport unless you clear your database first.'
23 | );
24 | }
25 | }
26 |
27 | async function isFirstRun() {
28 | const pluginStore = strapi.store({
29 | environment: strapi.config.environment,
30 | type: 'type',
31 | name: 'setup',
32 | });
33 | const initHasRun = await pluginStore.get({ key: 'initHasRun' });
34 | await pluginStore.set({ key: 'initHasRun', value: true });
35 | return !initHasRun;
36 | }
37 |
38 | async function setPublicPermissions(newPermissions) {
39 | // Find the ID of the public role
40 | const publicRole = await strapi.query('plugin::users-permissions.role').findOne({
41 | where: {
42 | type: 'public',
43 | },
44 | });
45 |
46 | // Create the new permissions and link them to the public role
47 | const allPermissionsToCreate = [];
48 | Object.keys(newPermissions).map((controller) => {
49 | const actions = newPermissions[controller];
50 | const permissionsToCreate = actions.map((action) => {
51 | return strapi.query('plugin::users-permissions.permission').create({
52 | data: {
53 | action: `api::${controller}.${controller}.${action}`,
54 | role: publicRole.id,
55 | },
56 | });
57 | });
58 | allPermissionsToCreate.push(...permissionsToCreate);
59 | });
60 | await Promise.all(allPermissionsToCreate);
61 | }
62 |
63 | function getFileSizeInBytes(filePath) {
64 | const stats = fs.statSync(filePath);
65 | const fileSizeInBytes = stats['size'];
66 | return fileSizeInBytes;
67 | }
68 |
69 | function getFileData(fileName) {
70 | const filePath = path.join('data', 'uploads', fileName);
71 | // Parse the file metadata
72 | const size = getFileSizeInBytes(filePath);
73 | const ext = fileName.split('.').pop();
74 | const mimeType = mime.lookup(ext || '') || '';
75 |
76 | return {
77 | filepath: filePath,
78 | originalFileName: fileName,
79 | size,
80 | mimetype: mimeType,
81 | };
82 | }
83 |
84 | async function uploadFile(file, name) {
85 | return strapi
86 | .plugin('upload')
87 | .service('upload')
88 | .upload({
89 | files: file,
90 | data: {
91 | fileInfo: {
92 | alternativeText: `An image uploaded to Strapi called ${name}`,
93 | caption: name,
94 | name,
95 | },
96 | },
97 | });
98 | }
99 |
100 | // Create an entry and attach files if there are any
101 | async function createEntry({ model, entry }) {
102 | try {
103 | // Actually create the entry in Strapi
104 | await strapi.documents(`api::${model}.${model}`).create({
105 | data: entry,
106 | });
107 | } catch (error) {
108 | console.error({ model, entry, error });
109 | }
110 | }
111 |
112 | async function checkFileExistsBeforeUpload(files) {
113 | const existingFiles = [];
114 | const uploadedFiles = [];
115 | const filesCopy = [...files];
116 |
117 | for (const fileName of filesCopy) {
118 | // Check if the file already exists in Strapi
119 | const fileWhereName = await strapi.query('plugin::upload.file').findOne({
120 | where: {
121 | name: fileName.replace(/\..*$/, ''),
122 | },
123 | });
124 |
125 | if (fileWhereName) {
126 | // File exists, don't upload it
127 | existingFiles.push(fileWhereName);
128 | } else {
129 | // File doesn't exist, upload it
130 | const fileData = getFileData(fileName);
131 | const fileNameNoExtension = fileName.split('.').shift();
132 | const [file] = await uploadFile(fileData, fileNameNoExtension);
133 | uploadedFiles.push(file);
134 | }
135 | }
136 | const allFiles = [...existingFiles, ...uploadedFiles];
137 | // If only one file then return only that file
138 | return allFiles.length === 1 ? allFiles[0] : allFiles;
139 | }
140 |
141 | async function updateBlocks(blocks) {
142 | const updatedBlocks = [];
143 | for (const block of blocks) {
144 | if (block.__component === 'shared.media') {
145 | const uploadedFiles = await checkFileExistsBeforeUpload([block.file]);
146 | // Copy the block to not mutate directly
147 | const blockCopy = { ...block };
148 | // Replace the file name on the block with the actual file
149 | blockCopy.file = uploadedFiles;
150 | updatedBlocks.push(blockCopy);
151 | } else if (block.__component === 'shared.slider') {
152 | // Get files already uploaded to Strapi or upload new files
153 | const existingAndUploadedFiles = await checkFileExistsBeforeUpload(block.files);
154 | // Copy the block to not mutate directly
155 | const blockCopy = { ...block };
156 | // Replace the file names on the block with the actual files
157 | blockCopy.files = existingAndUploadedFiles;
158 | // Push the updated block
159 | updatedBlocks.push(blockCopy);
160 | } else {
161 | // Just push the block as is
162 | updatedBlocks.push(block);
163 | }
164 | }
165 |
166 | return updatedBlocks;
167 | }
168 |
169 | async function importArticles() {
170 | for (const article of articles) {
171 | const cover = await checkFileExistsBeforeUpload([`${article.slug}.jpg`]);
172 | const updatedBlocks = await updateBlocks(article.blocks);
173 |
174 | await createEntry({
175 | model: 'article',
176 | entry: {
177 | ...article,
178 | cover,
179 | blocks: updatedBlocks,
180 | // Make sure it's not a draft
181 | publishedAt: Date.now(),
182 | },
183 | });
184 | }
185 | }
186 |
187 | async function importGlobal() {
188 | const favicon = await checkFileExistsBeforeUpload(['favicon.png']);
189 | const shareImage = await checkFileExistsBeforeUpload(['default-image.png']);
190 | return createEntry({
191 | model: 'global',
192 | entry: {
193 | ...global,
194 | favicon,
195 | // Make sure it's not a draft
196 | publishedAt: Date.now(),
197 | defaultSeo: {
198 | ...global.defaultSeo,
199 | shareImage,
200 | },
201 | },
202 | });
203 | }
204 |
205 | async function importAbout() {
206 | const updatedBlocks = await updateBlocks(about.blocks);
207 |
208 | await createEntry({
209 | model: 'about',
210 | entry: {
211 | ...about,
212 | blocks: updatedBlocks,
213 | // Make sure it's not a draft
214 | publishedAt: Date.now(),
215 | },
216 | });
217 | }
218 |
219 | async function importCategories() {
220 | for (const category of categories) {
221 | await createEntry({ model: 'category', entry: category });
222 | }
223 | }
224 |
225 | async function importAuthors() {
226 | for (const author of authors) {
227 | const avatar = await checkFileExistsBeforeUpload([author.avatar]);
228 |
229 | await createEntry({
230 | model: 'author',
231 | entry: {
232 | ...author,
233 | avatar,
234 | },
235 | });
236 | }
237 | }
238 |
239 | async function importSeedData() {
240 | // Allow read of application content types
241 | await setPublicPermissions({
242 | article: ['find', 'findOne'],
243 | category: ['find', 'findOne'],
244 | author: ['find', 'findOne'],
245 | global: ['find', 'findOne'],
246 | about: ['find', 'findOne'],
247 | });
248 |
249 | // Create all entries
250 | await importCategories();
251 | await importAuthors();
252 | await importArticles();
253 | await importGlobal();
254 | await importAbout();
255 | }
256 |
257 | async function main() {
258 | const { createStrapi, compileStrapi } = require('@strapi/strapi');
259 |
260 | const appContext = await compileStrapi();
261 | const app = await createStrapi(appContext).load();
262 |
263 | app.log.level = 'error';
264 |
265 | await seedExampleApp();
266 | await app.destroy();
267 |
268 | process.exit(0);
269 | }
270 |
271 | main().catch((error) => {
272 | console.error(error);
273 | process.exit(1);
274 | });
275 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/pages/HomePage/index.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * HomePage
4 | *
5 | */
6 | import { useState } from 'react';
7 | import { useAppInfo, useFetchClient } from '@strapi/strapi/admin';
8 | import {
9 | Box,
10 | Loader,
11 | Alert,
12 | Accordion,
13 | Modal,
14 | Button,
15 | Combobox,
16 | ComboboxOption,
17 | Flex,
18 | TextButton,
19 | Typography,
20 | } from '@strapi/design-system';
21 | import { Plus, Check } from '@strapi/icons';
22 | import RouteAccordion from '../../components/RouteAccordion';
23 | import serverRestartWatcher from '../../utils/serverRestartWatcher';
24 | import { Layouts } from '@strapi/admin/strapi-admin';
25 | import { useQuery } from 'react-query';
26 |
27 | export default function HomePage() {
28 | //const { lockAppWithAutoreload, unlockAppWithAutoreload } = useAutoReloadOverlayBlocker();
29 | const { get, put } = useFetchClient();
30 | const autoReload = useAppInfo('ProtectedPopulate', (state) => state.autoReload);
31 | const [expandedID, setExpandedID] = useState(null);
32 |
33 | const [modelRoute, setModelRoute] = useState('');
34 | const [modelContentType, setModelContentType] = useState('');
35 | const [oldData, setOldData] = useState('{}');
36 | const [selectedCheckboxes, setSelectedCheckboxes] = useState('{}');
37 | function updateSelectedCheckboxes() {
38 | const json = JSON.stringify(selectedCheckboxesClone);
39 | setSelectedCheckboxes(json);
40 | }
41 | const routesData = useQuery(['protected-routes'], async () => {
42 | const res = await get('/protected-populate/routes');
43 | const { data } = res;
44 | data.sort((a, b) => {
45 | const nameA = a.methods.at(-1) + ' ' + a.path;
46 | const nameB = b.methods.at(-1) + ' ' + b.path;
47 | return nameA.localeCompare(nameB);
48 | });
49 | return data;
50 | });
51 | const contenttypesData = useQuery(['protected-content-types'], async () => {
52 | const res = await get('/protected-populate/content-types');
53 | const { data } = res;
54 | return data;
55 | });
56 | const contenttypes2Data = useQuery(['protected-content-types2'], async () => {
57 | const res = await get('/content-type-builder/content-types');
58 | const { data } = res;
59 | return data.data;
60 | });
61 | const componentsData = useQuery(['protected-components'], async () => {
62 | const res = await get('/content-type-builder/components');
63 | const { data } = res;
64 | return data.data;
65 | });
66 | const dataData = useQuery(['protected-data'], async () => {
67 | const res = await get('/protected-populate/data');
68 | const { data } = res;
69 | setSelectedCheckboxes(JSON.stringify(data));
70 | setOldData(JSON.stringify(data));
71 | return data;
72 | });
73 | const roleData = useQuery(['protected-roles'], async () => {
74 | const res = await get('/users-permissions/roles');
75 | const { data } = res;
76 | return data;
77 | });
78 |
79 | if (
80 | routesData.isError ||
81 | contenttypesData.isError ||
82 | contenttypesData.isError ||
83 | contenttypes2Data.isError ||
84 | componentsData.isError ||
85 | dataData.isError ||
86 | roleData.isError
87 | ) {
88 | return (
89 |
90 | {error.toString()}
91 |
92 | );
93 | }
94 | if (
95 | routesData.isLoading ||
96 | contenttypesData.isLoading ||
97 | contenttypesData.isLoading ||
98 | contenttypes2Data.isLoading ||
99 | componentsData.isLoading ||
100 | dataData.isLoading ||
101 | roleData.isLoading
102 | )
103 | return ;
104 | let selectedCheckboxesClone = JSON.parse(selectedCheckboxes);
105 | const handleToggle = (id) => () => {
106 | setExpandedID((s) => (s === id ? null : id));
107 | };
108 | const handleFinish = () => {
109 | let data = JSON.parse(selectedCheckboxes);
110 | data[modelRoute] = {};
111 | data[modelRoute]['content-type'] = modelContentType;
112 | setSelectedCheckboxes(JSON.stringify(data));
113 | setModelContentType('');
114 | setModelRoute('');
115 | };
116 | const submitData = async () => {
117 | //lockAppWithAutoreload();
118 | await put('/protected-populate/data', JSON.parse(selectedCheckboxes))
119 | .then(() => {
120 | setOldData(selectedCheckboxes);
121 | })
122 | .catch((error) => {
123 | setError(error);
124 | });
125 | // Make sure the server has restarted
126 | await serverRestartWatcher(true);
127 |
128 | // Unlock the app
129 | //await unlockAppWithAutoreload();
130 | };
131 | //console.log(autoReload)
132 | console.log(roleData.data.roles);
133 | return (
134 |
135 |
138 | }
140 | onClick={() => submitData()}
141 | type="submit"
142 | disabled={oldData === selectedCheckboxes || !autoReload}
143 | >
144 | Save
145 |
146 |
147 | }
148 | title="Protected Routes"
149 | as="h2"
150 | />
151 |
152 |
153 |
154 | Add a new protected route
155 |
156 |
157 | select route
158 | {
161 | setModelRoute(value);
162 | }}
163 | >
164 | {routesData.data.map(function (route, i) {
165 | const name = route.methods.at(-1) + ' ' + route.path;
166 | if (typeof selectedCheckboxesClone[name] === 'undefined') {
167 | return (
168 |
169 | {name}
170 |
171 | );
172 | }
173 | })}
174 |
175 |
176 | select content type used
177 | {
181 | setModelContentType(value);
182 | }}
183 | >
184 | {contenttypesData.data.map(function (contentType, i) {
185 | return (
186 |
187 | {contentType}
188 |
189 | );
190 | })}
191 |
192 |
193 |
194 |
195 | Cancel
196 |
197 | handleFinish()}>Confirm
198 |
199 |
200 |
201 |
202 |
203 | {Object.keys(selectedCheckboxesClone).map(function (key, i) {
204 | return (
205 |
218 | );
219 | })}
220 |
221 |
222 |
223 | }>
224 | Add a new protected route
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 | );
235 | }
236 |
--------------------------------------------------------------------------------
/strapi-plugin-protected-populate/admin/src/components/RouteAccordion/index.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * HomePage
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { useState } from 'react';
9 | import { Select } from '@strapi/ui-primitives';
10 | import {
11 | Box,
12 | Accordion,
13 | MultiSelect,
14 | MultiSelectOption,
15 | IconButton,
16 | Checkbox,
17 | Typography,
18 | Modal,
19 | Button,
20 | } from '@strapi/design-system';
21 | import { Trash } from '@strapi/icons';
22 | import List from '../List';
23 | import RoleAccordion from '../RoleAccordion';
24 | const RouteAccordion = ({
25 | autoReload,
26 | routeName,
27 | handleToggle,
28 | expandedID,
29 | contentTypes,
30 | components,
31 | contentTypeNames,
32 | updateSelectedCheckboxes,
33 | selectedCheckboxes,
34 | roles,
35 | }) => {
36 | const [expandedIDRole, setExpandedIDRole] = useState(null);
37 | const handleToggleRole = (id) => () => {
38 | setExpandedIDRole((s) => (s === id ? null : id));
39 | };
40 |
41 | const [isVisible, setIsVisible] = useState(false);
42 | const [modelRoleCloneFrom, setModelRoleCloneFrom] = useState(null);
43 | const [modelRolesCloneTo, setModelRolesCloneTo] = useState([]);
44 |
45 | const handleSetIsVisible = (value, modelRoleCloneFrom) => {
46 | setModelRoleCloneFrom(modelRoleCloneFrom);
47 | setModelRolesCloneTo([]);
48 | setIsVisible(value);
49 | };
50 | const handleFinish = () => {
51 | for (const modelRole of modelRolesCloneTo) {
52 | selectedCheckboxes[routeName]['roles'][modelRole] = JSON.parse(
53 | JSON.stringify(selectedCheckboxes[routeName]['roles'][modelRoleCloneFrom])
54 | );
55 | }
56 | handleSetIsVisible(false);
57 | };
58 |
59 | const name = routeName;
60 | let typeInfo;
61 | if (selectedCheckboxes[routeName]['content-type'].includes('::')) {
62 | const index = contentTypes.findIndex(
63 | (data) => data.uid == selectedCheckboxes[routeName]['content-type']
64 | );
65 | typeInfo = contentTypes[index];
66 | } else {
67 | const index = components.findIndex(
68 | (data) => data.uid == selectedCheckboxes[routeName]['content-type']
69 | );
70 | typeInfo = components[index];
71 | }
72 | const rolesEnabled = typeof selectedCheckboxes[routeName]['roles'] !== 'undefined';
73 |
74 | const changeRolesEnabled = () => {
75 | console.log('Test123');
76 | if (rolesEnabled) {
77 | selectedCheckboxes[routeName]['populate'] =
78 | selectedCheckboxes[routeName]['roles']['public']['populate'];
79 | selectedCheckboxes[routeName]['fields'] =
80 | selectedCheckboxes[routeName]['roles']['public']['fields'];
81 | delete selectedCheckboxes[routeName]['roles'];
82 | } else {
83 | //enable roles
84 | selectedCheckboxes[routeName]['roles'] = {};
85 | for (const role of roles) {
86 | selectedCheckboxes[routeName]['roles'][role.type] = {};
87 | selectedCheckboxes[routeName]['roles'][role.type].populate =
88 | selectedCheckboxes[routeName]['populate'];
89 | selectedCheckboxes[routeName]['roles'][role.type].fields =
90 | selectedCheckboxes[routeName]['fields'];
91 | }
92 | delete selectedCheckboxes[routeName]['populate'];
93 | delete selectedCheckboxes[routeName]['fields'];
94 | }
95 | updateSelectedCheckboxes();
96 | };
97 |
98 | return (
99 |
100 |
101 | {name}
102 |
103 | {
105 | if (!autoReload) {
106 | return;
107 | }
108 | delete selectedCheckboxes[routeName];
109 | updateSelectedCheckboxes();
110 | }}
111 | label="Delete"
112 | >
113 |
114 |
115 |
116 |
117 |
118 |
119 | {
123 | if (!autoReload) {
124 | return;
125 | }
126 | selectedCheckboxes[routeName]['content-type'] = value;
127 | updateSelectedCheckboxes();
128 | }}
129 | >
130 |
131 | {contentTypeNames.map((object, i) => {
132 | if (!autoReload && selectedCheckboxes[routeName]['content-type'] !== object) {
133 | return;
134 | }
135 | return (
136 |
137 | {object}
138 |
139 | );
140 | })}
141 |
142 |
143 |
144 | Enable roles from user and permissions
145 | changeRolesEnabled()}
148 | checked={rolesEnabled}
149 | disabled={!autoReload}
150 | />
151 |
152 | {rolesEnabled ? (
153 |
154 |
155 |
156 |
157 | Clone Menu
158 |
159 |
160 |
161 | {
165 | setModelRolesCloneTo(value);
166 | }}
167 | multi
168 | withTags
169 | >
170 | {autoReload &&
171 | roles.map(function (role) {
172 | if (role.type !== modelRoleCloneFrom) {
173 | return (
174 |
175 | {role.name}
176 |
177 | );
178 | }
179 | })}
180 |
181 |
182 |
183 |
184 | Cancel
185 |
186 | handleFinish()}
189 | >
190 | Finish
191 |
192 |
193 |
194 | {roles.map((role, i) => {
195 | return (
196 |
211 | );
212 | })}
213 |
214 |
215 | ) : (
216 |
217 |
227 |
228 | )}
229 |
230 |
231 |
232 | );
233 | };
234 |
235 | export default RouteAccordion;
236 |
--------------------------------------------------------------------------------