Skip to content

Commit

Permalink
feat: strapi 3.1.0 rbac support (#4)
Browse files Browse the repository at this point in the history
* feat: strapi 3.1.0 rbac support
  • Loading branch information
cyp3rius authored Jul 23, 2020
1 parent f094851 commit eb36adf
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 107 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Complete installation requirements are exact same as for Strapi itself and can b

**Supported Strapi versions**:

- Strapi v3.0.6 (recently tested)
- Strapi v3.1.1 (recently tested)
- Strapi v3.x

(This plugin may work with the older Strapi versions, but these are not tested nor officially supported at this time.)
Expand Down
5 changes: 5 additions & 0 deletions admin/src/assets/images/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 49 additions & 45 deletions admin/src/components/AbuseReportsPopUp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ModalBody,
ModalForm,
useGlobalContext,
CheckPermissions,
} from 'strapi-helper-plugin';
import { Table, Button } from '@buffetjs/core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Expand All @@ -33,6 +34,7 @@ import pluginId from '../../pluginId';

// Translations
import en from '../../translations/en.json';
import pluginPermissions from '../../permissions';

const CustomRow = props => {
const { row, formatMessage, onClick } = props;
Expand Down Expand Up @@ -80,52 +82,54 @@ const AbuseReportsPopUp = ({ isOpen, reports, comment, blocked, blockedThread, o
}));

return (
<Modal isOpen={isOpen} onToggle={onClose}>
<HeaderModal>
<section>
<HeaderModalTitle>
<FormattedMessage id={`${pluginId}.popup.reports.header`} />
</HeaderModalTitle>
</section>
</HeaderModal>
<ModalForm>
<ModalBody>
<section style={{ width: '100%' }}>
<ItemPreviewContainer>
<ItemDetails root={true} {...comment} />
</ItemPreviewContainer>
{ isEmpty(reports) && (
<EmptyView>
<FontAwesomeIcon icon={faSmile} size="5x" />
<FormattedMessage id={`${pluginId}.popup.reports.empty`} />
</EmptyView>
) }
{ !isEmpty(reports) && (<TableContainer>
<Table
customRow={props => (<CustomRow {...props} formatMessage={formatMessage} onClick={(e) => onAbuseReportResolveClick(e, props.row.id)} />)}
headers={headers}
rows={rows}
/>
</TableContainer>)}
<CheckPermissions permissions={pluginPermissions.moderateReports}>
<Modal isOpen={isOpen} onToggle={onClose}>
<HeaderModal>
<section>
<HeaderModalTitle>
<FormattedMessage id={`${pluginId}.popup.reports.header`} />
</HeaderModalTitle>
</section>
</ModalBody>
</ModalForm>
{ !isEmpty(reports) && !blockedThread && !blocked && (<ModalFooter>
<section>
<label>
<FormattedMessage id={`${pluginId}.popup.reports.footer.actions`} />
</label>
{ !blockedThread && (<ButtonModal
onClick={e => onBlockClick(comment.id)}
isSecondary
message={`${pluginId}.list.item.moderation.button.comment.hide`} />)}
<ButtonModal
onClick={e => onBlockThreadClick(comment.id)}
isSecondary
message={`${pluginId}.list.item.moderation.button.thread.hide`} />
</section>
</ModalFooter>)}
</Modal>
</HeaderModal>
<ModalForm>
<ModalBody>
<section style={{ width: '100%' }}>
<ItemPreviewContainer>
<ItemDetails root={true} {...comment} />
</ItemPreviewContainer>
{ isEmpty(reports) && (
<EmptyView>
<FontAwesomeIcon icon={faSmile} size="5x" />
<FormattedMessage id={`${pluginId}.popup.reports.empty`} />
</EmptyView>
) }
{ !isEmpty(reports) && (<TableContainer>
<Table
customRow={props => (<CustomRow {...props} formatMessage={formatMessage} onClick={(e) => onAbuseReportResolveClick(e, props.row.id)} />)}
headers={headers}
rows={rows}
/>
</TableContainer>)}
</section>
</ModalBody>
</ModalForm>
{ !isEmpty(reports) && !blockedThread && !blocked && (<ModalFooter>
<section>
<label>
<FormattedMessage id={`${pluginId}.popup.reports.footer.actions`} />
</label>
{ !blockedThread && (<ButtonModal
onClick={e => onBlockClick(comment.id)}
isSecondary
message={`${pluginId}.list.item.moderation.button.comment.hide`} />)}
<ButtonModal
onClick={e => onBlockThreadClick(comment.id)}
isSecondary
message={`${pluginId}.list.item.moderation.button.thread.hide`} />
</section>
</ModalFooter>)}
</Modal>
</CheckPermissions>
);
}

Expand Down
33 changes: 20 additions & 13 deletions admin/src/components/ItemHeader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useGlobalContext } from 'strapi-helper-plugin';
import { useGlobalContext, CheckPermissions } from 'strapi-helper-plugin';
import { faLock, faStream, faAsterisk, faFire } from '@fortawesome/free-solid-svg-icons';
import Wrapper from './Wrapper';
import CardHeaderBlocked from './CardHeaderBlocked';
Expand All @@ -11,6 +11,7 @@ import CardHeaderIndicatorsContainer from './CardHeaderIndicatorsContainer';
import CardHeaderIndicatorBlue from './CardHeaderIndicatorBlue';
import CardHeaderIndicatorRed from './CardHeaderIndicatorRed';
import CardHeaderReports from './CardHeaderReports';
import pluginPermissions from '../../permissions';

const ItemHeader = ({ active, isDelailedView, blocked, blockedThread, isNew, isAbuseReported, abuseReports, onReportsClick }) => {
const { formatMessage } = useGlobalContext();
Expand All @@ -20,27 +21,33 @@ const ItemHeader = ({ active, isDelailedView, blocked, blockedThread, isNew, isA
return (
<Wrapper hasMargin={isBlocked || (isAbuseReported && isDelailedView)}>
{ isBlocked && (
<CardHeaderBlocked>
<FontAwesomeIcon icon={faLock} />
{blockedThread && <FontAwesomeIcon icon={faStream} />}
<FormattedMessage id={`${pluginId}.list.item.header.blocked${blockedThread ? '.thread' : ''}`} />
</CardHeaderBlocked>
<CheckPermissions permissions={pluginPermissions.moderate}>
<CardHeaderBlocked>
<FontAwesomeIcon icon={faLock} />
{blockedThread && <FontAwesomeIcon icon={faStream} />}
<FormattedMessage id={`${pluginId}.list.item.header.blocked${blockedThread ? '.thread' : ''}`} />
</CardHeaderBlocked>
</CheckPermissions>
) }
{ (isDelailedView && active) && (<>
{ isAbuseReported && (
<CardHeaderReports href="#abuse-reports" onClick={onReportsClick}>
<FontAwesomeIcon icon={faFire} />
<FormattedMessage id={`${pluginId}.list.item.header.abuse`} values={{ count: abuseReports.length }} />
</CardHeaderReports>
<CheckPermissions permissions={pluginPermissions.moderateReports}>
<CardHeaderReports href="#abuse-reports" onClick={onReportsClick}>
<FontAwesomeIcon icon={faFire} />
<FormattedMessage id={`${pluginId}.list.item.header.abuse`} values={{ count: abuseReports.length }} />
</CardHeaderReports>
</CheckPermissions>
)}
</>)}
{hasAnyIndicators && <CardHeaderIndicatorsContainer>
{ isNew && (<CardHeaderIndicatorBlue title={formatMessage({ id: `${pluginId}.list.item.indication.new`})}>
<FontAwesomeIcon icon={faAsterisk} />
</CardHeaderIndicatorBlue>) }
{ isAbuseReported && (<CardHeaderIndicatorRed title={formatMessage({ id: `${pluginId}.list.item.indication.abuse`})}>
<FontAwesomeIcon icon={faFire} />
</CardHeaderIndicatorRed>) }
<CheckPermissions permissions={pluginPermissions.moderateReports}>
{ isAbuseReported && (<CardHeaderIndicatorRed title={formatMessage({ id: `${pluginId}.list.item.indication.abuse`})}>
<FontAwesomeIcon icon={faFire} />
</CardHeaderIndicatorRed>) }
</CheckPermissions>
</CardHeaderIndicatorsContainer>
}
</Wrapper>
Expand Down
35 changes: 22 additions & 13 deletions admin/src/components/ItemModeration/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useGlobalContext } from 'strapi-helper-plugin';
import { useGlobalContext, CheckPermissions } from 'strapi-helper-plugin';
import { Button } from '@buffetjs/core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faComment, faCommentSlash, faComments } from '@fortawesome/free-solid-svg-icons';
import Wrapper from './Wrapper';
import pluginId from '../../pluginId';
import pluginPermissions from '../../permissions';

const ItemModeration = ({ id, blocked, blockedThread, onBlockClick, onBlockThreadClick }) => {

const { formatMessage } = useGlobalContext();

return (
<Wrapper>
{ !blockedThread && (<Button
onClick={e => onBlockClick(id)}
color={blocked ? 'primary' : 'delete'}
icon={<FontAwesomeIcon icon={blocked ? faComment : faCommentSlash} />}
label={formatMessage({ id: `${pluginId}.list.item.moderation.button.comment.${blocked ? 'restore' : 'hide'}` })} />)}
<Button
onClick={e => onBlockThreadClick(id)}
color={blockedThread ? 'primary' : 'delete'}
icon={<FontAwesomeIcon icon={faComments} />}
label={formatMessage({ id: `${pluginId}.list.item.moderation.button.thread.${blockedThread ? 'restore' : 'hide'}` })} />
</Wrapper>
<CheckPermissions permissions={pluginPermissions.moderate}>
<Wrapper>
{ !blockedThread && (
<CheckPermissions permissions={pluginPermissions.moderateComments}>
<Button
onClick={e => onBlockClick(id)}
color={blocked ? 'primary' : 'delete'}
icon={<FontAwesomeIcon icon={blocked ? faComment : faCommentSlash} />}
label={formatMessage({ id: `${pluginId}.list.item.moderation.button.comment.${blocked ? 'restore' : 'hide'}` })} />
</CheckPermissions>
)}
<CheckPermissions permissions={pluginPermissions.moderateThreads}>
<Button
onClick={e => onBlockThreadClick(id)}
color={blockedThread ? 'primary' : 'delete'}
icon={<FontAwesomeIcon icon={faComments} />}
label={formatMessage({ id: `${pluginId}.list.item.moderation.button.thread.${blockedThread ? 'restore' : 'hide'}` })} />
</CheckPermissions>
</Wrapper>
</CheckPermissions>
);
};

Expand Down
43 changes: 23 additions & 20 deletions admin/src/containers/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,40 @@

import React, { Suspense, lazy }from 'react';
import { Switch, Route } from 'react-router-dom';
import { NotFound, LoadingIndicatorPage } from 'strapi-helper-plugin';
import { NotFound, LoadingIndicatorPage, CheckPagePermissions } from 'strapi-helper-plugin';
import Wrapper from './Wrapper';
// Utils
import pluginId from '../../pluginId';
import DataManagerProvider from '../DataManagerProvider';
import pluginPermissions from '../../permissions';
// Containers


const Panel = lazy(() => import('../Panel'));

const App = () => {
return (
<Wrapper>
<DataManagerProvider>
<Suspense fallback={<LoadingIndicatorPage />}>
<Switch>
<Route
path={`/plugins/${pluginId}`}
component={Panel}
exact
/>
<Route
path={`/plugins/${pluginId}/display/:id`}
component={Panel}
exact
/>
<Route component={NotFound} />
</Switch>
</Suspense>
</DataManagerProvider>
</Wrapper>
<CheckPagePermissions permissions={pluginPermissions.main}>
<Wrapper>
<DataManagerProvider>
<Suspense fallback={<LoadingIndicatorPage />}>
<Switch>
<Route
path={`/plugins/${pluginId}`}
component={Panel}
exact
/>
<Route
path={`/plugins/${pluginId}/display/:id`}
component={Panel}
exact
/>
<Route component={NotFound} />
</Switch>
</Suspense>
</DataManagerProvider>
</Wrapper>
</CheckPagePermissions>
);
};

Expand Down
25 changes: 21 additions & 4 deletions admin/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import pluginPermissions from './permissions';
import pluginLogo from './assets/images/logo.svg';
import App from './containers/App';
import Initializer from './containers/Initializer';
import lifecycles from './lifecycles';
Expand All @@ -8,24 +10,39 @@ import trads from './translations';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;

const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon: pluginPkg.strapi.icon,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
isReady: false,
isRequired: pluginPkg.strapi.required || false,
layout: null,
lifecycles,
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: App,
name: pluginPkg.strapi.name,
name,
pluginLogo,
preventComponentRendering: false,
trads,
menu: {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`,
icon,
name,
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'COMMENTS',
},
permissions: pluginPermissions.main,
},
],
},
};

return strapi.registerPlugin(plugin);
Expand Down
34 changes: 34 additions & 0 deletions admin/src/permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const pluginPermissions = {
// This permission regards the main component (App) and is used to tell
// If the plugin link should be displayed in the menu
// And also if the plugin is accessible. This use case is found when a user types the url of the
// plugin directly in the browser
main: [
{ action: 'plugins::comments.read', subject: null },
{ action: 'plugins::comments.moderate.block.comment', subject: null },
{ action: 'plugins::comments.moderate.block.thread', subject: null },
{ action: 'plugins::comments.moderate.reports', subject: null },
],
open: [
{ action: 'plugins::comments.read', subject: null },
],
moderate: [
{ action: 'plugins::comments.read', subject: null },
{ action: 'plugins::comments.moderate.block.comment', subject: null },
{ action: 'plugins::comments.moderate.block.thread', subject: null },
],
moderateComments: [
{ action: 'plugins::comments.read', subject: null },
{ action: 'plugins::comments.moderate.block.comment', subject: null },
],
moderateThreads: [
{ action: 'plugins::comments.read', subject: null },
{ action: 'plugins::comments.moderate.block.thread', subject: null },
],
moderateReports: [
{ action: 'plugins::comments.read', subject: null },
{ action: 'plugins::comments.moderate.reports', subject: null },
],
};

export default pluginPermissions;
Loading

0 comments on commit eb36adf

Please sign in to comment.