├── 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 | 83 | 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 |
79 | 80 | Name 81 | 82 | 84 | 85 | Type 86 | 87 |
110 |
111 |
112 | ); 113 | } 114 | 115 | export default List; 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Strapi Protected Populate Plugin

3 | 4 |

5 | 6 | Strapi Discord 7 | 8 | 9 | NPM Version 10 | 11 | 12 | Monthly download on NPM 13 | 14 |

15 |
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 |
2 |

Strapi Protected Populate Plugin

3 | 4 |

5 | 6 | Strapi Discord 7 | 8 | 9 | NPM Version 10 | 11 | 12 | Monthly download on NPM 13 | 14 |

15 |
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 | 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 | 196 | 197 | 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 | 185 | 186 | 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 | --------------------------------------------------------------------------------