Remove staff util functions, normalize account staff fields

This commit is contained in:
Alex Gleason 2022-04-01 19:35:57 -05:00
parent 4a8f08e313
commit 1e3c6d9430
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
20 changed files with 135 additions and 212 deletions

View File

@ -2,81 +2,85 @@
exports[`<EmojiSelector /> renders correctly 1`] = `
<div
className="emoji-react-selector-container"
onBlur={[Function]}
onFocus={[Function]}
tabIndex="-1"
>
<div
className="emoji-react-selector w-max"
onBlur={[Function]}
className="flex space-x-2 bg-white dark:bg-slate-900 p-3 rounded-full shadow-md z-[999] w-max"
>
<button
className="emoji-react-selector__emoji"
dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"👍\\" title=\\":+1:\\" src=\\"/packs/emoji/1f44d.svg\\" />",
}
}
className=""
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={-1}
/>
>
<img
alt="👍"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f44d.svg"
/>
</button>
<button
className="emoji-react-selector__emoji"
dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"❤\\" title=\\":heart:\\" src=\\"/packs/emoji/2764.svg\\" />",
}
}
className=""
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={-1}
/>
>
<img
alt="❤"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/2764.svg"
/>
</button>
<button
className="emoji-react-selector__emoji"
dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😆\\" title=\\":laughing:\\" src=\\"/packs/emoji/1f606.svg\\" />",
}
}
className=""
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={-1}
/>
>
<img
alt="😆"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f606.svg"
/>
</button>
<button
className="emoji-react-selector__emoji"
dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😮\\" title=\\":open_mouth:\\" src=\\"/packs/emoji/1f62e.svg\\" />",
}
}
className=""
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={-1}
/>
>
<img
alt="😮"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f62e.svg"
/>
</button>
<button
className="emoji-react-selector__emoji"
dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😢\\" title=\\":cry:\\" src=\\"/packs/emoji/1f622.svg\\" />",
}
}
className=""
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={-1}
/>
>
<img
alt="😢"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f622.svg"
/>
</button>
<button
className="emoji-react-selector__emoji"
dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😩\\" title=\\":weary:\\" src=\\"/packs/emoji/1f629.svg\\" />",
}
}
className=""
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={-1}
/>
>
<img
alt="😩"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f629.svg"
/>
</button>
</div>
</div>
`;

View File

@ -17,7 +17,6 @@ import ActionButton from 'soapbox/features/ui/components/action_button';
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
import { UserPanel } from 'soapbox/features/ui/util/async-components';
import { makeGetAccount } from 'soapbox/selectors';
import { isAdmin, isModerator } from 'soapbox/utils/accounts';
import { showProfileHoverCard } from './hover_ref_wrapper';
import { Card, CardBody, Stack, Text } from './ui';
@ -27,9 +26,9 @@ const getAccount = makeGetAccount();
const getBadges = (account) => {
const badges = [];
if (isAdmin(account)) {
if (account.admin) {
badges.push(<Badge key='admin' slug='admin' title='Admin' />);
} else if (isModerator(account)) {
} else if (account.moderator) {
badges.push(<Badge key='moderator' slug='moderator' title='Moderator' />);
}

View File

@ -70,7 +70,7 @@ const SidebarNavigation = () => {
)
)}
{/* {(account && isStaff(account)) && (
{/* {(account && account.staff) && (
<SidebarNavigationLink
to='/admin'
icon={location.pathname.startsWith('/admin') ? require('icons/dashboard-filled.svg') : require('@tabler/icons/icons/dashboard.svg')}

View File

@ -15,7 +15,6 @@ import { getFeatures } from 'soapbox/utils/features';
import { closeSidebar } from '../actions/sidebar';
import { makeGetAccount, makeGetOtherAccounts } from '../selectors';
import { isAdmin, isStaff } from '../utils/accounts';
import { HStack, Icon, IconButton, Text } from './ui';
@ -155,7 +154,7 @@ const SidebarMenu = () => {
<Account account={account} showProfileHoverCard={false} />
</Link>
{isStaff(account) && (
{account.staff && (
<Stack>
<button type='button' onClick={handleSwitcherClick} className='py-1'>
<HStack alignItems='center' justifyContent='between'>
@ -232,7 +231,7 @@ const SidebarMenu = () => {
/>
)}
{isAdmin(account) && (
{account.admin && (
<SidebarLink
to='/soapbox/config'
icon={require('@tabler/icons/icons/settings.svg')}

View File

@ -15,7 +15,6 @@ import {
} from 'soapbox/components/ui/status/status-action-button';
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
import { isUserTouching } from 'soapbox/is_mobile';
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji_reacts';
import { getFeatures } from 'soapbox/utils/features';
@ -739,8 +738,8 @@ const mapStateToProps = (state: RootState) => {
return {
me,
isStaff: account ? isStaff(account) : false,
isAdmin: account ? isAdmin(account) : false,
isStaff: account ? account.staff : false,
isAdmin: account ? account.admin : false,
features: getFeatures(instance),
};
};

View File

@ -57,7 +57,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
)
)}
{/* (account && isStaff(account)) && (
{/* (account && account.staff && (
<ThumbNavigationLink
src={require('@tabler/icons/icons/dashboard.svg')}
text={<FormattedMessage id='navigation.dashboard' defaultMessage='Dashboard' />}

View File

@ -18,12 +18,8 @@ import StillImage from 'soapbox/components/still_image';
import { HStack, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuLink, MenuDivider } from 'soapbox/components/ui';
import ActionButton from 'soapbox/features/ui/components/action_button';
import {
isStaff,
isAdmin,
isModerator,
isLocal,
isRemote,
getDomain,
} from 'soapbox/utils/accounts';
import { getFeatures } from 'soapbox/utils/features';
@ -322,7 +318,7 @@ class Header extends ImmutablePureComponent {
}
if (isRemote(account)) {
const domain = getDomain(account);
const domain = account.fqn.split('@')[1];
menu.push(null);
@ -341,10 +337,10 @@ class Header extends ImmutablePureComponent {
}
}
if (isStaff(meAccount)) {
if (meAccount.staff) {
menu.push(null);
if (isAdmin(meAccount)) {
if (meAccount.admin) {
menu.push({
text: intl.formatMessage(messages.admin_account, { name: account.get('username') }),
to: `/pleroma/admin/#/users/${account.id}/`,
@ -353,8 +349,8 @@ class Header extends ImmutablePureComponent {
});
}
if (account.get('id') !== me && isLocal(account) && isAdmin(meAccount)) {
if (isAdmin(account)) {
if (account.id !== me && isLocal(account) && meAccount.admin) {
if (account.admin) {
menu.push({
text: intl.formatMessage(messages.demoteToModerator, { name: account.get('username') }),
action: this.props.onPromoteToModerator,
@ -365,7 +361,7 @@ class Header extends ImmutablePureComponent {
action: this.props.onDemoteToUser,
icon: require('@tabler/icons/icons/arrow-down-circle.svg'),
});
} else if (isModerator(account)) {
} else if (account.moderator) {
menu.push({
text: intl.formatMessage(messages.promoteToAdmin, { name: account.get('username') }),
action: this.props.onPromoteToAdmin,
@ -404,7 +400,7 @@ class Header extends ImmutablePureComponent {
});
}
if (features.suggestionsV2 && isAdmin(meAccount)) {
if (features.suggestionsV2 && meAccount.admin) {
if (account.getIn(['pleroma', 'is_suggested'])) {
menu.push({
text: intl.formatMessage(messages.unsuggestUser, { name: account.get('username') }),

View File

@ -37,7 +37,6 @@ import { initReport } from 'soapbox/actions/reports';
import { getSettings } from 'soapbox/actions/settings';
import snackbar from 'soapbox/actions/snackbar';
import { makeGetAccount } from 'soapbox/selectors';
import { isAdmin } from 'soapbox/utils/accounts';
import Header from '../components/header';
@ -216,7 +215,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onPromoteToModerator(account) {
const messageType = isAdmin(account) ? messages.demotedToModerator : messages.promotedToModerator;
const messageType = account.admin ? messages.demotedToModerator : messages.promotedToModerator;
const message = intl.formatMessage(messageType, { acct: account.get('acct') });
dispatch(promoteToModerator(account.get('id')))

View File

@ -7,7 +7,6 @@ import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email_list';
import { isAdmin } from 'soapbox/utils/accounts';
import sourceCode from 'soapbox/utils/code';
import { parseVersion } from 'soapbox/utils/features';
import { getFeatures } from 'soapbox/utils/features';
@ -139,7 +138,7 @@ class Dashboard extends ImmutablePureComponent {
</div>
</div>
</div>
{isAdmin(account) && <RegistrationModePicker />}
{account.admin && <RegistrationModePicker />}
<div className='dashwidgets'>
<div className='dashwidget'>
<h4><FormattedMessage id='admin.dashwidgets.software_header' defaultMessage='Software' /></h4>
@ -148,7 +147,7 @@ class Dashboard extends ImmutablePureComponent {
<li>{v.software} <span className='pull-right'>{v.version}</span></li>
</ul>
</div>
{supportsEmailList && isAdmin(account) && <div className='dashwidget'>
{supportsEmailList && account.admin && <div className='dashwidget'>
<h4><FormattedMessage id='admin.dashwidgets.email_list_header' defaultMessage='Email list' /></h4>
<ul>
<li><a href='#' onClick={this.handleSubscribersClick} target='_blank'>subscribers.csv</a></li>

View File

@ -7,7 +7,6 @@ import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { isUserTouching } from 'soapbox/is_mobile';
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
import { getReactForStatus } from 'soapbox/utils/emoji_reacts';
import { getFeatures } from 'soapbox/utils/features';
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
@ -65,8 +64,8 @@ const mapStateToProps = state => {
return {
me,
isStaff: account ? isStaff(account) : false,
isAdmin: account ? isAdmin(account) : false,
isStaff: account ? account.staff : false,
isAdmin: account ? account.admin : false,
features: getFeatures(instance),
};
};

View File

@ -11,7 +11,6 @@ import { openModal } from 'soapbox/actions/modals';
import DropdownMenu from 'soapbox/containers/dropdown_menu_container';
import InstanceRestrictions from 'soapbox/features/federation_restrictions/components/instance_restrictions';
import { makeGetRemoteInstance } from 'soapbox/selectors';
import { isAdmin } from 'soapbox/utils/accounts';
const getRemoteInstance = makeGetRemoteInstance();
@ -20,13 +19,13 @@ const messages = defineMessages({
});
const mapStateToProps = (state, { host }) => {
const me = state.get('me');
const account = state.getIn(['accounts', me]);
const { me, instance } = state;
const account = state.accounts.get(me);
return {
instance: state.get('instance'),
instance,
remoteInstance: getRemoteInstance(state, host),
isAdmin: isAdmin(account),
isAdmin: account.admin,
};
};

View File

@ -9,7 +9,6 @@ import { fetchOwnAccounts } from 'soapbox/actions/auth';
import { Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
import { isStaff } from 'soapbox/utils/accounts';
import Account from '../../../components/account';
@ -31,7 +30,7 @@ type IMenuItem = {
action?: (event: React.MouseEvent) => void
}
const getAccount: any = makeGetAccount();
const getAccount = makeGetAccount();
const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
const dispatch = useDispatch();
@ -40,7 +39,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
const me = useAppSelector((state) => state.me);
const currentAccount = useAppSelector((state) => getAccount(state, me));
const authUsers = useAppSelector((state) => state.auth.get('users'));
const isCurrentAccountStaff = isStaff(currentAccount) || false;
const isCurrentAccountStaff = Boolean(currentAccount?.staff);
const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.get('id'))));
const handleLogOut = () => {

View File

@ -12,7 +12,7 @@ import { initAccountNoteModal } from 'soapbox/actions/account_notes';
import Badge from 'soapbox/components/badge';
import { Icon, HStack, Stack, Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification_badge';
import { getAcct, isAdmin, isModerator, isLocal } from 'soapbox/utils/accounts';
import { isLocal } from 'soapbox/utils/accounts';
import { displayFqn } from 'soapbox/utils/state';
import ProfileStats from './profile_stats';
@ -48,9 +48,9 @@ class ProfileInfoPanel extends ImmutablePureComponent {
getStaffBadge = () => {
const { account } = this.props;
if (isAdmin(account)) {
if (account?.admin) {
return <Badge slug='admin' title='Admin' key='staff' />;
} else if (isModerator(account)) {
} else if (account?.moderator) {
return <Badge slug='moderator' title='Moderator' key='staff' />;
} else {
return null;
@ -155,7 +155,7 @@ class ProfileInfoPanel extends ImmutablePureComponent {
{verified && <VerificationBadge />}
{account.get('bot') && <Badge slug='bot' title={intl.formatMessage(messages.bot)} />}
{account.bot && <Badge slug='bot' title={intl.formatMessage(messages.bot)} />}
{badges.length > 0 && (
<HStack space={1} alignItems='center'>
@ -166,7 +166,7 @@ class ProfileInfoPanel extends ImmutablePureComponent {
<HStack alignItems='center' space={0.5}>
<Text size='sm' theme='muted'>
@{getAcct(account, displayFqn)}
@{displayFqn ? account.fqn : account.acct}
</Text>
{account.get('locked') && (

View File

@ -26,7 +26,6 @@ import HomePage from 'soapbox/pages/home_page';
import ProfilePage from 'soapbox/pages/profile_page';
import RemoteInstancePage from 'soapbox/pages/remote_instance_page';
import StatusPage from 'soapbox/pages/status_page';
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
import { getAccessToken } from 'soapbox/utils/auth';
import { getVapidKey } from 'soapbox/utils/auth';
import { getFeatures } from 'soapbox/utils/features';
@ -495,12 +494,12 @@ class UI extends React.PureComponent {
dispatch(fetchChats());
}
if (isStaff(account)) {
if (account.staff) {
dispatch(fetchReports({ state: 'open' }));
dispatch(fetchUsers(['local', 'need_approval']));
}
if (isAdmin(account)) {
if (account.admin) {
dispatch(fetchConfig());
}

View File

@ -5,7 +5,6 @@ import { connect } from 'react-redux';
import { Redirect, Route } from 'react-router-dom';
import { getSettings } from 'soapbox/actions/settings';
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
import BundleColumnError from '../components/bundle_column_error';
import ColumnForbidden from '../components/column_forbidden';
@ -111,8 +110,8 @@ class WrappedRoute extends React.Component {
const authorized = [
account || publicRoute,
developerOnly ? settings.get('isDeveloper') : true,
staffOnly ? account && isStaff(account) : true,
adminOnly ? account && isAdmin(account) : true,
staffOnly ? account && account.staff : true,
adminOnly ? account && account.admin : true,
].every(c => c);
if (!authorized) {

View File

@ -168,4 +168,13 @@ describe('normalizeAccount()', () => {
expect(result.fqn).toEqual('benis911@mastodon.social');
});
it('normalizes Pleroma staff', () => {
const account = require('soapbox/__fixtures__/pleroma-account.json');
const result = normalizeAccount(account);
expect(result.admin).toBe(true);
expect(result.staff).toBe(true);
expect(result.moderator).toBe(false);
});
});

View File

@ -50,12 +50,15 @@ export const AccountRecord = ImmutableRecord({
verified: false,
// Internal fields
admin: false,
display_name_html: '',
moderator: false,
note_emojified: '',
note_plain: '',
patron: ImmutableMap<string, any>(),
relationship: ImmutableList<ImmutableMap<string, any>>(),
should_refetch: false,
staff: false,
});
// https://docs.joinmastodon.org/entities/field/
@ -221,6 +224,18 @@ const normalizeFqn = (account: ImmutableMap<string, any>) => {
return account.set('fqn', fqn);
};
const addStaffFields = (account: ImmutableMap<string, any>) => {
const admin = account.getIn(['pleroma', 'is_admin']) === true;
const moderator = account.getIn(['pleroma', 'is_moderator']) === true;
const staff = admin || moderator;
return account.merge({
admin,
moderator,
staff,
});
};
export const normalizeAccount = (account: Record<string, any>) => {
return AccountRecord(
ImmutableMap(fromJS(account)).withMutations(account => {
@ -233,6 +248,7 @@ export const normalizeAccount = (account: Record<string, any>) => {
normalizeBirthday(account);
normalizeLocation(account);
normalizeFqn(account);
addStaffFields(account);
fixUsername(account);
fixDisplayName(account);
addInternalFields(account);

View File

@ -11,19 +11,18 @@ import {
InstanceInfoPanel,
InstanceModerationPanel,
} from 'soapbox/features/ui/util/async-components';
import { isAdmin } from 'soapbox/utils/accounts';
import { federationRestrictionsDisclosed } from 'soapbox/utils/state';
import { Layout } from '../components/ui';
const mapStateToProps = state => {
const me = state.get('me');
const account = state.getIn(['accounts', me]);
const me = state.me;
const account = state.accounts.get(me);
return {
me,
disclosed: federationRestrictionsDisclosed(state),
isAdmin: isAdmin(account),
isAdmin: Boolean(account?.admin),
};
};

View File

@ -1,94 +1,15 @@
import { fromJS } from 'immutable';
import {
getDomain,
isStaff,
isAdmin,
isModerator,
} from '../accounts';
describe('getDomain', () => {
const account = fromJS({
acct: 'alice',
url: 'https://party.com/users/alice',
});
it('returns the domain', () => {
expect(getDomain(account)).toEqual('party.com');
});
});
describe('isStaff', () => {
describe('with empty user', () => {
const account = fromJS({});
it('returns false', () => {
expect(isStaff(account)).toBe(false);
});
});
describe('with Pleroma admin', () => {
const admin = fromJS({ pleroma: { is_admin: true } });
it('returns true', () => {
expect(isStaff(admin)).toBe(true);
});
});
describe('with Pleroma moderator', () => {
const mod = fromJS({ pleroma: { is_moderator: true } });
it('returns true', () => {
expect(isStaff(mod)).toBe(true);
});
});
describe('with undefined', () => {
const account = undefined;
it('returns false', () => {
expect(isStaff(account)).toBe(false);
});
});
});
describe('isAdmin', () => {
describe('with empty user', () => {
const account = fromJS({});
it('returns false', () => {
expect(isAdmin(account)).toBe(false);
});
});
describe('with Pleroma admin', () => {
const admin = fromJS({ pleroma: { is_admin: true } });
it('returns true', () => {
expect(isAdmin(admin)).toBe(true);
});
});
describe('with Pleroma moderator', () => {
const mod = fromJS({ pleroma: { is_moderator: true } });
it('returns false', () => {
expect(isAdmin(mod)).toBe(false);
});
});
});
describe('isModerator', () => {
describe('with empty user', () => {
const account = fromJS({});
it('returns false', () => {
expect(isModerator(account)).toBe(false);
});
});
describe('with Pleroma admin', () => {
const admin = fromJS({ pleroma: { is_admin: true } });
it('returns false', () => {
expect(isModerator(admin)).toBe(false);
});
});
describe('with Pleroma moderator', () => {
const mod = fromJS({ pleroma: { is_moderator: true } });
it('returns true', () => {
expect(isModerator(mod)).toBe(true);
});
});
});
// import { fromJS } from 'immutable';
//
// import {
// getDomain,
// } from '../accounts';
//
// describe('getDomain', () => {
// const account = fromJS({
// acct: 'alice',
// url: 'https://party.com/users/alice',
// });
// it('returns the domain', () => {
// expect(getDomain(account)).toEqual('party.com');
// });
// });

View File

@ -1,6 +1,6 @@
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
import { Account } from 'soapbox/types/entities';
import type { Account } from 'soapbox/types/entities';
const getDomainFromURL = (account: Account): string => {
try {
@ -29,18 +29,6 @@ export const getAcct = (account: Account, displayFqn: boolean): string => (
displayFqn === true ? account.fqn : account.acct
);
export const isStaff = (account: Account): boolean => (
[isAdmin, isModerator].some(f => f(account) === true)
);
export const isAdmin = (account: Account): boolean => (
account.getIn(['pleroma', 'is_admin']) === true
);
export const isModerator = (account: Account): boolean => (
account.getIn(['pleroma', 'is_moderator']) === true
);
export const getFollowDifference = (state: ImmutableMap<string, any>, accountId: string, type: string): number => {
const items: any = state.getIn(['user_lists', type, accountId, 'items'], ImmutableOrderedSet());
const counter: number = Number(state.getIn(['accounts_counters', accountId, `${type}_count`], 0));