feat: 更多的权限点与前端权限显示

pull/49/head
moonrailgun 3 years ago
parent 439749ca4e
commit 76c898ba92

@ -184,6 +184,10 @@ export {
useGroupTextPanelUnread, useGroupTextPanelUnread,
} from './redux/hooks/useGroup'; } from './redux/hooks/useGroup';
export { useGroupMemberMute } from './redux/hooks/useGroupMemberMute'; export { useGroupMemberMute } from './redux/hooks/useGroupMemberMute';
export {
useGroupMemberAllPermissions,
useHasGroupPermission,
} from './redux/hooks/useGroupPermission';
export { useUserInfo, useUserId } from './redux/hooks/useUserInfo'; export { useUserInfo, useUserId } from './redux/hooks/useUserInfo';
export { useUnread } from './redux/hooks/useUnread'; export { useUnread } from './redux/hooks/useUnread';
export { export {
@ -218,6 +222,13 @@ export {
export { isValidStr } from './utils/string-helper'; export { isValidStr } from './utils/string-helper';
export { isValidJson } from './utils/json-helper'; export { isValidJson } from './utils/json-helper';
export { MessageHelper } from './utils/message-helper'; export { MessageHelper } from './utils/message-helper';
export {
PERMISSION,
AllPermission,
permissionList,
getDefaultPermissionList,
applyDefaultFallbackGroupPermission,
} from './utils/role-helper';
export { uploadFile } from './utils/upload-helper'; export { uploadFile } from './utils/upload-helper';
export type { UploadFileResult } from './utils/upload-helper'; export type { UploadFileResult } from './utils/upload-helper';
export { parseUrlStr } from './utils/url-helper'; export { parseUrlStr } from './utils/url-helper';

@ -30,6 +30,7 @@
"devDependencies": { "devDependencies": {
"@types/crc": "^3.4.0", "@types/crc": "^3.4.0",
"@types/lodash": "^4.14.170", "@types/lodash": "^4.14.170",
"@types/react": "^17.0.39",
"@types/react-redux": "^7.1.24", "@types/react-redux": "^7.1.24",
"react": "17.0.2" "react": "17.0.2"
}, },

@ -0,0 +1,72 @@
import { useGroupInfo } from './useGroup';
import { useUserId } from './useUserInfo';
import _uniq from 'lodash/uniq';
import _flatten from 'lodash/flatten';
import { useDebugValue, useMemo } from 'react';
import { permissionList } from '../..';
/**
*
*/
export function useGroupMemberAllPermissions(groupId: string): string[] {
const groupInfo = useGroupInfo(groupId);
const userId = useUserId();
if (!groupInfo || !userId) {
return [];
}
if (groupInfo.owner === userId) {
// 群组管理员拥有一切权限
// 返回所有权限
return permissionList.map((p) => p.key);
}
const members = groupInfo.members;
const groupRoles = groupInfo.roles;
const userRoles = members.find((m) => m.userId === userId)?.roles ?? [];
const userPermissions = _uniq([
..._flatten(
userRoles.map(
(roleId) =>
groupRoles.find((role) => String(role._id) === roleId)?.permissions ??
[]
)
),
...groupInfo.fallbackPermissions,
]);
useDebugValue({
groupRoles,
userRoles,
userPermissions,
fallbackPermissions: groupInfo.fallbackPermissions,
});
return userPermissions;
}
/**
*
*/
export function useHasGroupPermission(
groupId: string,
permissions: string[]
): boolean[] {
const userPermissions = useGroupMemberAllPermissions(groupId);
const result = useMemo(
() => permissions.map((p) => userPermissions.includes(p)),
[userPermissions.join(','), permissions.join(',')]
);
useDebugValue({
groupId,
userPermissions,
checkedPermissions: permissions,
result,
});
return result;
}

@ -0,0 +1,100 @@
import { model, t } from '..';
/**
*
*
*/
export const AllPermission = Symbol('AllPermission');
interface PermissionItem {
key: string;
title: string;
desc: string;
default: boolean;
required?: string[];
}
export const PERMISSION = {
/**
* core
*/
core: {
message: 'core.message',
invite: 'core.invite',
unlimitedInvite: 'core.unlimitedInvite',
groupDetail: 'core.groupDetail',
managePanel: 'core.managePanel',
manageInvite: 'core.manageInvite',
manageRoles: 'core.manageRoles',
},
};
/**
* TODO:
*/
export const permissionList: PermissionItem[] = [
{
key: PERMISSION.core.message,
title: t('发送消息'),
desc: t('允许成员在文字频道发送消息'),
default: true,
},
{
key: PERMISSION.core.invite,
title: t('邀请链接'),
desc: t('允许成员创建邀请链接'),
default: true,
},
{
key: PERMISSION.core.unlimitedInvite,
title: t('不限时邀请链接'),
desc: t('允许成员创建不限时邀请链接'),
default: false,
required: [PERMISSION.core.invite],
},
{
key: PERMISSION.core.groupDetail,
title: t('查看群组详情'),
desc: t('允许成员查看群组详情'),
default: false,
},
{
key: PERMISSION.core.managePanel,
title: t('允许管理频道'),
desc: t('允许成员查看管理频道'),
default: false,
required: [PERMISSION.core.groupDetail],
},
{
key: PERMISSION.core.manageInvite,
title: t('允许管理邀请链接'),
desc: t('允许成员管理邀请链接'),
default: false,
required: [PERMISSION.core.groupDetail],
},
{
key: PERMISSION.core.manageRoles,
title: t('允许管理身份组'),
desc: t('允许成员管理身份组'),
default: false,
required: [PERMISSION.core.groupDetail],
},
];
/**
*
*/
export function getDefaultPermissionList(): string[] {
return permissionList.filter((p) => p.default).map((p) => p.key);
}
/**
*
*/
export async function applyDefaultFallbackGroupPermission(groupId: string) {
await model.group.modifyGroupField(
groupId,
'fallbackPermissions',
getDefaultPermissionList()
);
}

@ -18,6 +18,9 @@ import _uniq from 'lodash/uniq';
interface ChatInputBoxProps { interface ChatInputBoxProps {
onSendMsg: (msg: string, meta?: SendMessagePayloadMeta) => void; onSendMsg: (msg: string, meta?: SendMessagePayloadMeta) => void;
} }
/**
*
*/
export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => { export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');

@ -9,6 +9,8 @@ import {
t, t,
humanizeMsDuration, humanizeMsDuration,
useInterval, useInterval,
useHasGroupPermission,
PERMISSION,
} from 'tailchat-shared'; } from 'tailchat-shared';
import { GroupPanelWrapper } from './Wrapper'; import { GroupPanelWrapper } from './Wrapper';
@ -18,6 +20,9 @@ import { GroupPanelWrapper } from './Wrapper';
function useChatInputInfo(groupId: string) { function useChatInputInfo(groupId: string) {
const userId = useUserId(); const userId = useUserId();
const muteUntil = useGroupMemberMute(groupId, userId ?? ''); const muteUntil = useGroupMemberMute(groupId, userId ?? '');
const [hasPermission] = useHasGroupPermission(groupId, [
PERMISSION.core.message,
]);
const [placeholder, setPlaceholder] = useState<string | undefined>(undefined); const [placeholder, setPlaceholder] = useState<string | undefined>(undefined);
const updatePlaceholder = useCallback(() => { const updatePlaceholder = useCallback(() => {
@ -44,6 +49,13 @@ function useChatInputInfo(groupId: string) {
updatePlaceholder(); updatePlaceholder();
}, [muteUntil]); }, [muteUntil]);
if (!hasPermission) {
return {
disabled: true,
placeholder: t('没有发送消息的权限, 请联系群组所有者'),
};
}
return { return {
disabled: Boolean(muteUntil), disabled: Boolean(muteUntil),
placeholder, placeholder,

@ -13,7 +13,7 @@ import { Avatar } from '../Avatar';
import { closeModal, ModalWrapper } from '../Modal'; import { closeModal, ModalWrapper } from '../Modal';
import { Slides, SlidesRef } from '../Slides'; import { Slides, SlidesRef } from '../Slides';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { applyDefaultFallbackGroupPermission } from '@/utils/role-helper'; import { applyDefaultFallbackGroupPermission } from 'tailchat-shared';
const panelTemplate: { const panelTemplate: {
key: string; key: string;

@ -7,6 +7,8 @@ import {
createGroupInviteCode, createGroupInviteCode,
t, t,
GroupInvite, GroupInvite,
PERMISSION,
useHasGroupPermission,
} from 'tailchat-shared'; } from 'tailchat-shared';
import styles from './CreateInviteCode.module.less'; import styles from './CreateInviteCode.module.less';
@ -30,10 +32,16 @@ export const CreateInviteCode: React.FC<CreateInviteCodeProps> = React.memo(
}, },
[groupId, onInviteCreated] [groupId, onInviteCreated]
); );
const [hasInvitePermission, hasUnlimitedInvitePermission] =
useHasGroupPermission(groupId, [
PERMISSION.core.invite,
PERMISSION.core.unlimitedInvite,
]);
const menu = ( const menu = (
<Menu> <Menu>
<Menu.Item <Menu.Item
disabled={!hasUnlimitedInvitePermission}
onClick={() => handleCreateInviteLink(InviteCodeType.Permanent)} onClick={() => handleCreateInviteLink(InviteCodeType.Permanent)}
> >
{t('创建永久邀请码')} {t('创建永久邀请码')}
@ -61,6 +69,7 @@ export const CreateInviteCode: React.FC<CreateInviteCodeProps> = React.memo(
className={styles.createInviteBtn} className={styles.createInviteBtn}
size="large" size="large"
type="primary" type="primary"
disabled={!hasInvitePermission}
loading={loading} loading={loading}
onClick={() => handleCreateInviteLink(InviteCodeType.Normal)} onClick={() => handleCreateInviteLink(InviteCodeType.Normal)}
overlay={menu} overlay={menu}

@ -4,6 +4,7 @@ import React from 'react';
interface PermissionItemProps { interface PermissionItemProps {
title: string; title: string;
desc?: string; desc?: string;
disabled?: boolean;
checked: boolean; checked: boolean;
onChange: (checked: boolean) => void; onChange: (checked: boolean) => void;
} }
@ -18,7 +19,11 @@ export const PermissionItem: React.FC<PermissionItemProps> = React.memo(
</Col> </Col>
<Col> <Col>
<Switch checked={props.checked} onChange={props.onChange} /> <Switch
disabled={props.disabled}
checked={props.checked}
onChange={props.onChange}
/>
</Col> </Col>
</Row> </Row>

@ -1,6 +1,6 @@
import { Loading } from '@/components/Loading'; import { Loading } from '@/components/Loading';
import { PillTabPane, PillTabs } from '@/components/PillTabs'; import { PillTabPane, PillTabs } from '@/components/PillTabs';
import { AllPermission } from '@/utils/role-helper'; import { AllPermission } from 'tailchat-shared';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { t, useGroupInfo } from 'tailchat-shared'; import { t, useGroupInfo } from 'tailchat-shared';
import { RoleItem } from './RoleItem'; import { RoleItem } from './RoleItem';

@ -1,4 +1,4 @@
import { AllPermission, permissionList } from '@/utils/role-helper'; import { AllPermission, permissionList } from 'tailchat-shared';
import { Button } from 'antd'; import { Button } from 'antd';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { model, t } from 'tailchat-shared'; import { model, t } from 'tailchat-shared';
@ -54,6 +54,11 @@ export const RolePermission: React.FC<RolePermissionProps> = React.memo(
key={p.key} key={p.key}
title={p.title} title={p.title}
desc={p.desc} desc={p.desc}
disabled={
p.required
? !p.required.every((r) => editingPermission.includes(r))
: undefined
}
checked={editingPermission.includes(p.key)} checked={editingPermission.includes(p.key)}
onChange={(checked) => handleSwitchPermission(p.key, checked)} onChange={(checked) => handleSwitchPermission(p.key, checked)}
/> />

@ -1,4 +1,4 @@
import { AllPermission, getDefaultPermissionList } from '@/utils/role-helper'; import { AllPermission, getDefaultPermissionList } from 'tailchat-shared';
import { model, t, useAsyncRequest } from 'tailchat-shared'; import { model, t, useAsyncRequest } from 'tailchat-shared';
export function useRoleActions( export function useRoleActions(

@ -7,11 +7,12 @@ import {
import { GroupIdContextProvider } from '@/context/GroupIdContext'; import { GroupIdContextProvider } from '@/context/GroupIdContext';
import { pluginCustomPanel } from '@/plugin/common'; import { pluginCustomPanel } from '@/plugin/common';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { t } from 'tailchat-shared'; import { PERMISSION, t, useHasGroupPermission } from 'tailchat-shared';
import { GroupInvite } from './Invite'; import { GroupInvite } from './Invite';
import { GroupPanel } from './Panel'; import { GroupPanel } from './Panel';
import { GroupRole } from './Role'; import { GroupRole } from './Role';
import { GroupSummary } from './Summary'; import { GroupSummary } from './Summary';
import _compact from 'lodash/compact';
interface SettingsViewProps { interface SettingsViewProps {
groupId: string; groupId: string;
@ -27,6 +28,12 @@ export const GroupDetail: React.FC<SettingsViewProps> = React.memo((props) => {
}, },
[props.onClose] [props.onClose]
); );
const [allowManagePanel, allowManageInvite, allowManageRoles] =
useHasGroupPermission(groupId, [
PERMISSION.core.managePanel,
PERMISSION.core.manageInvite,
PERMISSION.core.manageRoles,
]);
const menu: SidebarViewMenuType[] = useMemo(() => { const menu: SidebarViewMenuType[] = useMemo(() => {
// 内置 // 内置
@ -34,29 +41,29 @@ export const GroupDetail: React.FC<SettingsViewProps> = React.memo((props) => {
{ {
type: 'group', type: 'group',
title: t('通用'), title: t('通用'),
children: [ children: _compact([
{ {
type: 'item', type: 'item',
title: t('概述'), title: t('概述'),
content: <GroupSummary groupId={groupId} />, content: <GroupSummary groupId={groupId} />,
}, },
{ allowManagePanel && {
type: 'item', type: 'item',
title: t('面板'), title: t('面板'),
content: <GroupPanel groupId={groupId} />, content: <GroupPanel groupId={groupId} />,
}, },
{ allowManageInvite && {
type: 'item', type: 'item',
title: t('邀请码'), title: t('邀请码'),
content: <GroupInvite groupId={groupId} />, content: <GroupInvite groupId={groupId} />,
}, },
{ allowManageRoles && {
type: 'item', type: 'item',
title: t('身份组'), title: t('身份组'),
isDev: true, isDev: true,
content: <GroupRole groupId={groupId} />, content: <GroupRole groupId={groupId} />,
}, },
], ]),
}, },
]; ];

@ -1,7 +1,12 @@
import React from 'react'; import React from 'react';
import { Menu } from 'antd'; import { Menu } from 'antd';
import _isNil from 'lodash/isNil'; import _isNil from 'lodash/isNil';
import { useGroupInfo, useTranslation } from 'tailchat-shared'; import {
PERMISSION,
useGroupInfo,
useHasGroupPermission,
useTranslation,
} from 'tailchat-shared';
import { SectionHeader } from '@/components/SectionHeader'; import { SectionHeader } from '@/components/SectionHeader';
import { useGroupHeaderAction } from './useGroupHeaderAction'; import { useGroupHeaderAction } from './useGroupHeaderAction';
@ -12,8 +17,12 @@ export const GroupHeader: React.FC<GroupHeaderProps> = React.memo((props) => {
const { groupId } = props; const { groupId } = props;
const groupInfo = useGroupInfo(groupId); const groupInfo = useGroupInfo(groupId);
const { t } = useTranslation(); const { t } = useTranslation();
const [showGroupDetail, showInvite] = useHasGroupPermission(groupId, [
PERMISSION.core.groupDetail,
PERMISSION.core.invite,
]);
const { isOwner, handleShowGroupDetail, handleInviteUser, handleQuitGroup } = const { handleShowGroupDetail, handleInviteUser, handleQuitGroup } =
useGroupHeaderAction(groupId); useGroupHeaderAction(groupId);
if (_isNil(groupInfo)) { if (_isNil(groupInfo)) {
@ -22,13 +31,13 @@ export const GroupHeader: React.FC<GroupHeaderProps> = React.memo((props) => {
const menu = ( const menu = (
<Menu> <Menu>
{isOwner && ( {showGroupDetail && (
<Menu.Item key="0" onClick={handleShowGroupDetail}> <Menu.Item key="0" onClick={handleShowGroupDetail}>
{t('查看详情')} {t('查看详情')}
</Menu.Item> </Menu.Item>
)} )}
{isOwner && ( {showInvite && (
<Menu.Item key="1" onClick={handleInviteUser}> <Menu.Item key="1" onClick={handleInviteUser}>
{t('邀请用户')} {t('邀请用户')}
</Menu.Item> </Menu.Item>

@ -47,5 +47,5 @@ export function useGroupHeaderAction(groupId: string) {
} }
}); });
return { isOwner, handleShowGroupDetail, handleInviteUser, handleQuitGroup }; return { handleShowGroupDetail, handleInviteUser, handleQuitGroup };
} }

@ -1,31 +0,0 @@
import { model, t } from 'tailchat-shared';
/**
*
*
*/
export const AllPermission = Symbol('AllPermission');
export const permissionList = [
{
key: 'core.message',
title: t('发送消息'),
desc: t('允许成员在文字频道发送消息'),
default: true,
},
];
export function getDefaultPermissionList(): string[] {
return permissionList.filter((p) => p.default).map((p) => p.key);
}
/**
*
*/
export async function applyDefaultFallbackGroupPermission(groupId: string) {
await model.group.modifyGroupField(
groupId,
'fallbackPermissions',
getDefaultPermissionList()
);
}

@ -123,6 +123,7 @@ importers:
'@reduxjs/toolkit': ^1.7.1 '@reduxjs/toolkit': ^1.7.1
'@types/crc': ^3.4.0 '@types/crc': ^3.4.0
'@types/lodash': ^4.14.170 '@types/lodash': ^4.14.170
'@types/react': ^17.0.39
'@types/react-redux': ^7.1.24 '@types/react-redux': ^7.1.24
axios: ^0.21.1 axios: ^0.21.1
crc: ^3.8.0 crc: ^3.8.0
@ -166,6 +167,7 @@ importers:
devDependencies: devDependencies:
'@types/crc': 3.8.0 '@types/crc': 3.8.0
'@types/lodash': 4.14.184 '@types/lodash': 4.14.184
'@types/react': 17.0.48
'@types/react-redux': 7.1.24 '@types/react-redux': 7.1.24
react: 17.0.2 react: 17.0.2
@ -2983,7 +2985,7 @@ packages:
react-dom: '*' react-dom: '*'
dependencies: dependencies:
'@docusaurus/types': 2.0.0-beta.18 '@docusaurus/types': 2.0.0-beta.18
'@types/react': 17.0.48 '@types/react': 18.0.17
'@types/react-router-config': 5.0.6 '@types/react-router-config': 5.0.6
'@types/react-router-dom': 5.3.3 '@types/react-router-dom': 5.3.3
react: 17.0.2 react: 17.0.2
@ -3295,7 +3297,7 @@ packages:
peerDependencies: peerDependencies:
react: '*' react: '*'
dependencies: dependencies:
'@types/react': 17.0.48 '@types/react': 18.0.17
prop-types: 15.8.1 prop-types: 15.8.1
react: 17.0.2 react: 17.0.2
@ -15191,7 +15193,7 @@ packages:
pretty-format: 27.5.1 pretty-format: 27.5.1
slash: 3.0.0 slash: 3.0.0
strip-json-comments: 3.1.1 strip-json-comments: 3.1.1
ts-node: 10.9.1_t4lrjbt3sxauai4t5o275zsepa ts-node: 10.9.1_bqee57coj3oib6dw4m24wknwqe
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- canvas - canvas

Loading…
Cancel
Save