refactor: support org

This commit is contained in:
fantasticit 2022-06-30 14:20:38 +08:00
parent 3e0eb5dc92
commit 808f7877eb
89 changed files with 1249 additions and 1723 deletions

View File

@ -23,6 +23,7 @@ server:
enableRateLimit: true # 是否限流 enableRateLimit: true # 是否限流
rateLimitWindowMs: 60000 # 限流时间 rateLimitWindowMs: 60000 # 限流时间
rateLimitMax: 1000 # 单位限流时间内单个 ip 最大访问数量 rateLimitMax: 1000 # 单位限流时间内单个 ip 最大访问数量
enableEmailVerify: false
email: # 邮箱服务,参考 http://help.163.com/09/1223/14/5R7P6CJ600753VB8.html?servCode=6010376 获取 SMTP 配置 email: # 邮箱服务,参考 http://help.163.com/09/1223/14/5R7P6CJ600753VB8.html?servCode=6010376 获取 SMTP 配置
host: '' host: ''
port: 465 port: 465

View File

@ -30,6 +30,7 @@ export const System = () => {
<Banner type="warning" description="系统锁定后,除系统管理员外均不可登录,谨慎修改!" closeIcon={null} /> <Banner type="warning" description="系统锁定后,除系统管理员外均不可登录,谨慎修改!" closeIcon={null} />
<Form labelPosition="left" initValues={data} onChange={onFormChange} onSubmit={onFinish}> <Form labelPosition="left" initValues={data} onChange={onFormChange} onSubmit={onFinish}>
<Form.Switch field="isSystemLocked" label="系统锁定" /> <Form.Switch field="isSystemLocked" label="系统锁定" />
<Form.Switch field="enableEmailVerify" label="邮箱检验" />
<Button <Button
htmlType="submit" htmlType="submit"

View File

@ -20,7 +20,18 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
const { user: currentUser } = useUser(); const { user: currentUser } = useUser();
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const [collaborationUsers, setCollaborationUsers] = useState([]); const [collaborationUsers, setCollaborationUsers] = useState([]);
const content = useMemo(() => <Members id={documentId} hook={useDoumentMembers} />, [documentId]); const content = useMemo(
() => (
<div style={{ padding: '24px 0' }}>
<Members
id={documentId}
hook={useDoumentMembers}
descriptions={['权限继承:默认继承知识库成员权限', '超级管理员:知识库超级管理员和文档创建者']}
/>
</div>
),
[documentId]
);
const btn = useMemo( const btn = useMemo(
() => ( () => (
<Button theme="borderless" type="tertiary" disabled={disabled} icon={<IconUserAdd />} onClick={toggleVisible} /> <Button theme="borderless" type="tertiary" disabled={disabled} icon={<IconUserAdd />} onClick={toggleVisible} />
@ -79,7 +90,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
visible={visible} visible={visible}
footer={null} footer={null}
onCancel={toggleVisible} onCancel={toggleVisible}
style={{ maxWidth: '96vw' }} style={{ maxWidth: '96vw', maxHeight: '60vh', overflow: 'auto' }}
> >
{content} {content}
</Modal> </Modal>
@ -97,6 +108,8 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
width: 412, width: 412,
maxWidth: '96vw', maxWidth: '96vw',
padding: '0 24px', padding: '0 24px',
maxHeight: '60vh',
overflow: 'auto',
}} }}
> >
{content} {content}

View File

@ -1,5 +1,5 @@
import { IconDelete } from '@douyinfe/semi-icons'; import { IconDelete } from '@douyinfe/semi-icons';
import { Modal, Popconfirm, Space, Typography } from '@douyinfe/semi-ui'; import { Popconfirm, Space, Typography } from '@douyinfe/semi-ui';
import { useDeleteDocument } from 'data/document'; import { useDeleteDocument } from 'data/document';
import { useRouterQuery } from 'hooks/use-router-query'; import { useRouterQuery } from 'hooks/use-router-query';
import Router from 'next/router'; import Router from 'next/router';
@ -15,8 +15,11 @@ interface IProps {
const { Text } = Typography; const { Text } = Typography;
export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render, onDelete }) => { export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render, onDelete }) => {
const { wikiId: currentWikiId, documentId: currentDocumentId } = const {
useRouterQuery<{ wikiId?: string; documentId?: string }>(); organizationId,
wikiId: currentWikiId,
documentId: currentDocumentId,
} = useRouterQuery<{ organizationId: string; wikiId?: string; documentId?: string }>();
const { deleteDocument: api, loading } = useDeleteDocument(documentId); const { deleteDocument: api, loading } = useDeleteDocument(documentId);
const deleteAction = useCallback(() => { const deleteAction = useCallback(() => {
@ -26,14 +29,15 @@ export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render,
return; return;
} }
Router.push({ Router.push({
pathname: `/wiki/${wikiId}`, pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
query: { organizationId, wikiId },
}); });
}; };
navigate(); navigate();
onDelete && onDelete(); onDelete && onDelete();
}); });
}, [wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]); }, [organizationId, wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]);
const content = useMemo( const content = useMemo(
() => ( () => (

View File

@ -1,16 +1,11 @@
import { IAuthority, ILoginUser } from '@think/domains'; import { IAuthority, ILoginUser } from '@think/domains';
import cls from 'classnames'; import cls from 'classnames';
import { useDoumentMembers } from 'data/document';
import { event, triggerChangeDocumentTitle, triggerJoinUser, USE_DOCUMENT_VERSION } from 'event'; import { event, triggerChangeDocumentTitle, triggerJoinUser, USE_DOCUMENT_VERSION } from 'event';
import { useMount } from 'hooks/use-mount'; import { useMount } from 'hooks/use-mount';
import { useToggle } from 'hooks/use-toggle'; import React, { useEffect, useRef } from 'react';
import Router from 'next/router';
import React, { useEffect, useRef, useState } from 'react';
import { CollaborationEditor, ICollaborationRefProps } from 'tiptap/editor'; import { CollaborationEditor, ICollaborationRefProps } from 'tiptap/editor';
import { findMentions } from 'tiptap/prose-utils';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { DocumentUserSetting } from './users';
interface IProps { interface IProps {
user: ILoginUser; user: ILoginUser;
@ -19,12 +14,8 @@ interface IProps {
} }
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority }) => { export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority }) => {
const $hasShowUserSettingModal = useRef(false);
const $editor = useRef<ICollaborationRefProps>(); const $editor = useRef<ICollaborationRefProps>();
const mounted = useMount(); const mounted = useMount();
const { users, addUser, updateUser } = useDoumentMembers(documentId);
const [mentionUsersSettingVisible, toggleMentionUsersSettingVisible] = useToggle(false);
const [mentionUsers, setMentionUsers] = useState([]);
useEffect(() => { useEffect(() => {
const handler = (data) => { const handler = (data) => {
@ -39,56 +30,6 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
}; };
}, []); }, []);
useEffect(() => {
const handler = () => {
const editor = $editor.current && $editor.current.getEditor();
if (!editor) return;
// 已经拦截过一次,不再拦截
if ($hasShowUserSettingModal.current) return;
const mentionUsers = findMentions(editor);
if (!mentionUsers || !mentionUsers.length) return;
const currentUserAuth = users.find((user) => {
return user.user.name === currentUser.name;
});
const isCurrentUserCreateUser = currentUserAuth.auth.createUserId === currentUser.id;
if (!isCurrentUserCreateUser) {
return;
}
const data = Array.from(new Set(mentionUsers))
.filter((userName) => {
const exist = users.find((user) => {
return user.user.name === userName;
});
if (!exist || !exist.auth.readable) return true;
return false;
})
.filter(Boolean);
if (!data.length) return;
setMentionUsers(data);
toggleMentionUsersSettingVisible(true);
$hasShowUserSettingModal.current = true;
// ignore-me
const newErr = new Error('请完成权限操作后关闭页面');
throw newErr;
};
Router.events.on('routeChangeStart', handler);
window.addEventListener('unload', handler);
return () => {
$hasShowUserSettingModal.current = false;
Router.events.off('routeChangeStart', handler);
window.removeEventListener('unload', handler);
};
}, [users, currentUser, toggleMentionUsersSettingVisible]);
return ( return (
<div className={cls(styles.editorWrap)}> <div className={cls(styles.editorWrap)}>
{mounted && ( {mounted && (
@ -103,14 +44,6 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
onAwarenessUpdate={triggerJoinUser} onAwarenessUpdate={triggerJoinUser}
/> />
)} )}
<DocumentUserSetting
visible={mentionUsersSettingVisible}
toggleVisible={toggleMentionUsersSettingVisible}
mentionUsers={mentionUsers}
users={users}
addUser={addUser}
updateUser={updateUser}
/>
</div> </div>
); );
}; };

View File

@ -1,94 +0,0 @@
import { Checkbox, Modal, Table, Typography } from '@douyinfe/semi-ui';
import { IAuthority, IUser } from '@think/domains';
import { DocAuth } from 'data/document';
import React, { useMemo } from 'react';
interface IProps {
visible: boolean;
toggleVisible: (arg: boolean) => void;
mentionUsers: string[];
users: Array<{ user: IUser; auth: IAuthority }>;
addUser: (auth: DocAuth) => Promise<unknown>;
updateUser: (auth: DocAuth) => Promise<unknown>;
}
const { Text } = Typography;
const { Column } = Table;
// eslint-disable-next-line react/display-name
const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked, data) => {
const handle = (evt) => {
const ret = {
...data,
};
ret[authKey] = evt.target.checked;
onChange(ret);
};
return <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
};
export const DocumentUserSetting: React.FC<IProps> = ({
visible,
toggleVisible,
mentionUsers,
users,
addUser,
updateUser,
}) => {
const renderUsers = useMemo(() => {
return mentionUsers
.map((mentionUser) => {
const exist = users.find((user) => {
return user.user.name === mentionUser;
});
if (!exist) return { userName: mentionUser, readable: false, editable: false, shouldAddToDocument: true };
return {
userName: mentionUser,
readable: exist.auth.readable,
editable: exist.auth.editable,
shouldAddToDocument: false,
};
})
.filter(Boolean);
}, [users, mentionUsers]);
const handler = async (data) => {
if (data.shouldAddToDocument) {
await addUser(data.userName);
}
await updateUser(data);
};
return (
<Modal
title={'权限操作'}
visible={visible}
onCancel={() => toggleVisible(false)}
maskClosable={false}
style={{ maxWidth: '96vw' }}
footer={null}
>
<Text> @ </Text>
<Table style={{ margin: '24px 0' }} dataSource={renderUsers} size="small" pagination>
<Column title="用户名" dataIndex="userName" key="name" />
<Column
title="是否可读"
dataIndex="readable"
key="readable"
render={renderChecked(handler, 'readable')}
align="center"
/>
<Column
title="是否可编辑"
dataIndex="editable"
key="editable"
render={renderChecked(handler, 'editable')}
align="center"
/>
</Table>
</Modal>
);
};

View File

@ -1,14 +1,12 @@
import { Banner, Button, Input, Modal, Select, Space } from '@douyinfe/semi-ui'; import { Banner, Input, Popconfirm, Select, Space } from '@douyinfe/semi-ui';
import { AuthEnum, AuthEnumArray } from '@think/domains'; import { AuthEnum, AuthEnumArray } from '@think/domains';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
interface IProps { interface IProps {
visible: boolean;
toggleVisible: (arg) => void;
onOk: (arg) => any; onOk: (arg) => any;
} }
export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => { export const AddUser: React.FC<IProps> = ({ onOk, children }) => {
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess); const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const [userName, setUserName] = useState(''); const [userName, setUserName] = useState('');
@ -16,47 +14,46 @@ export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
onOk({ userName, userAuth }).then(() => { onOk({ userName, userAuth }).then(() => {
setUserAuth(AuthEnum.noAccess); setUserAuth(AuthEnum.noAccess);
setUserName(''); setUserName('');
toggleVisible(false);
}); });
}, [onOk, userName, userAuth, toggleVisible]); }, [onOk, userName, userAuth]);
return ( return (
<Modal <Popconfirm
zIndex={1070}
title={'添加成员'} title={'添加成员'}
okText={'邀请对方'} okText={'邀请对方'}
visible={visible} style={{ maxWidth: '96vw', width: 380 }}
onOk={handleOk} onConfirm={handleOk}
onCancel={() => toggleVisible(false)} okButtonProps={{
maskClosable={false} disabled: !userName,
style={{ maxWidth: '96vw' }} }}
footer={null} content={
<div style={{ margin: '16px -68px 0 0' }}>
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
<Space>
<Select value={userAuth} onChange={setUserAuth} style={{ width: 120 }} zIndex={1080}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
<Input
autofocus
placeholder="输入对方用户名"
value={userName}
onChange={setUserName}
style={{ width: 160 }}
></Input>
</Space>
</div>
}
> >
<div style={{ marginTop: 16 }}> {children}
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? ( </Popconfirm>
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
<Space>
<Select value={userAuth} onChange={setUserAuth} style={{ width: 120 }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
<Input
autofocus
placeholder="输入对方用户名"
value={userName}
onChange={setUserName}
style={{ width: 270 }}
></Input>
</Space>
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk} disabled={!userName}>
</Button>
</div>
</Modal>
); );
}; };

View File

@ -1,58 +1,44 @@
import { Banner, Button, Modal, Select } from '@douyinfe/semi-ui'; import { Banner, Popconfirm, Select, Toast } from '@douyinfe/semi-ui';
import { AuthEnum, AuthEnumArray, IAuth, IUser } from '@think/domains'; import { AuthEnum, AuthEnumArray, IAuth, IUser } from '@think/domains';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useState } from 'react';
interface IProps { interface IProps {
visible: boolean; userWithAuth: { user: IUser; auth: IAuth };
toggleVisible: (arg) => void; updateUser: (arg) => any;
currentUser: { user: IUser; auth: IAuth };
onOk: (arg) => any;
} }
export const EditUser: React.FC<IProps> = ({ visible, toggleVisible, currentUser, onOk }) => { export const EditUser: React.FC<IProps> = ({ userWithAuth, updateUser, children }) => {
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess); const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const handleOk = useCallback(() => { const handleOk = useCallback(() => {
onOk(userAuth).then(() => { return updateUser({ userName: userWithAuth.user.name, userAuth }).then(() => {
setUserAuth(AuthEnum.noAccess); Toast.success('操作成功');
toggleVisible(false);
}); });
}, [onOk, userAuth, toggleVisible]); }, [updateUser, userAuth, userWithAuth]);
useEffect(() => {
if (!visible) {
setUserAuth(AuthEnum.noAccess);
}
}, [visible]);
return ( return (
<Modal <Popconfirm
title={`修改用户${currentUser && currentUser.user.name}权限`} title={`修改用户${userWithAuth && userWithAuth.user.name}权限`}
visible={visible} content={
onOk={handleOk} <div style={{ margin: '16px -68px 0 0' }}>
onCancel={() => toggleVisible(false)} {[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
maskClosable={false} <Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
style={{ maxWidth: '96vw' }} ) : null}
footer={null} {}
<Select value={userAuth} onChange={setUserAuth} style={{ width: '100%' }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
</div>
}
onConfirm={handleOk}
> >
<div style={{ marginTop: 16 }}> {children}
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? ( </Popconfirm>
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
{}
<Select value={userAuth} onChange={setUserAuth} style={{ width: '100%' }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk}>
</Button>
</div>
</Modal>
); );
}; };

View File

@ -1,11 +1,9 @@
import { IconDelete, IconEdit } from '@douyinfe/semi-icons'; import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
import { Banner, Button, Popconfirm, Table, Typography } from '@douyinfe/semi-ui'; import { Banner, Button, Popconfirm, Table, Typography } from '@douyinfe/semi-ui';
import { AuthEnumTextMap, IOrganization } from '@think/domains'; import { AuthEnumTextMap } from '@think/domains';
import { DataRender } from 'components/data-render'; import { DataRender } from 'components/data-render';
import { LocaleTime } from 'components/locale-time'; import { LocaleTime } from 'components/locale-time';
import { useOrganizationMembers } from 'data/organization'; import React from 'react';
import { useToggle } from 'hooks/use-toggle';
import React, { useState } from 'react';
import { AddUser } from './add'; import { AddUser } from './add';
import { EditUser } from './edit'; import { EditUser } from './edit';
@ -14,33 +12,17 @@ import styles from './index.module.scss';
interface IProps { interface IProps {
id: string; id: string;
hook: any; hook: any;
descriptions?: Array<string>;
} }
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
const { Column } = Table; const { Column } = Table;
export const Members: React.FC<IProps> = ({ id, hook }) => { export const Members: React.FC<IProps> = ({ id, hook, descriptions }) => {
const { data, loading, error, addUser, updateUser, deleteUser } = hook(id); const { data, loading, error, page, pageSize, setPage, addUser, updateUser, deleteUser } = hook(id);
const [visible, toggleVisible] = useToggle(false);
const [editVisible, toggleEditVisible] = useToggle(false);
const [currentUser, setCurrentUser] = useState(null);
const editUser = (user) => {
setCurrentUser(user);
toggleEditVisible(true);
};
const handleEdit = (userAuth) => {
return updateUser({ userName: currentUser.user.name, userAuth }).then(() => {
setCurrentUser(null);
});
};
console.log(data);
return ( return (
<div className={styles.wrap}> <div className={styles.wrap}>
<header>{/* <MemberAdder /> */}</header>
<DataRender <DataRender
loading={loading} loading={loading}
error={error} error={error}
@ -55,19 +37,36 @@ export const Members: React.FC<IProps> = ({ id, hook }) => {
title={<Title heading={6}></Title>} title={<Title heading={6}></Title>}
description={ description={
<div> <div>
<Paragraph></Paragraph> {descriptions && descriptions.length ? (
<Paragraph></Paragraph> descriptions.map((desc) => {
<Paragraph>访</Paragraph> return <Paragraph key={desc}>{desc}</Paragraph>;
})
) : (
<>
<Paragraph></Paragraph>
<Paragraph></Paragraph>
<Paragraph>访</Paragraph>
</>
)}
</div> </div>
} }
/> />
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button theme="solid" onClick={toggleVisible}> <AddUser onOk={addUser}>
<Button theme="solid"></Button>
</Button> </AddUser>
</div> </div>
<Table style={{ margin: '16px 0' }} dataSource={data.data} size="small" pagination={false}> <Table
style={{ margin: '16px 0' }}
dataSource={data.data}
size="small"
pagination={{
currentPage: page,
pageSize,
total: data.total,
onPageChange: setPage,
}}
>
<Column title="用户名" dataIndex="user.name" key="user.name" /> <Column title="用户名" dataIndex="user.name" key="user.name" />
<Column <Column
title="成员权限" title="成员权限"
@ -90,7 +89,9 @@ export const Members: React.FC<IProps> = ({ id, hook }) => {
align="center" align="center"
render={(_, data) => ( render={(_, data) => (
<> <>
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={() => editUser(data)} /> <EditUser userWithAuth={data} updateUser={updateUser}>
<Button type="tertiary" theme="borderless" icon={<IconEdit />} />
</EditUser>
<Popconfirm <Popconfirm
showArrow showArrow
title="确认删除该成员?" title="确认删除该成员?"
@ -105,9 +106,6 @@ export const Members: React.FC<IProps> = ({ id, hook }) => {
</div> </div>
)} )}
/> />
<AddUser visible={visible} toggleVisible={toggleVisible} onOk={addUser} />
<EditUser visible={editVisible} toggleVisible={toggleEditVisible} currentUser={currentUser} onOk={handleEdit} />
</div> </div>
); );
}; };

View File

@ -0,0 +1,49 @@
import { IconDelete } from '@douyinfe/semi-icons';
import { Modal, Space, Typography } from '@douyinfe/semi-ui';
import { IOrganization } from '@think/domains';
import { useOrganizationDetail } from 'data/organization';
import { useRouterQuery } from 'hooks/use-router-query';
import Router from 'next/router';
import React, { useCallback } from 'react';
interface IProps {
organizationId: IOrganization['id'];
onDelete?: () => void;
}
const { Text } = Typography;
export const OrganizationDeletor: React.FC<IProps> = ({ organizationId, onDelete, children }) => {
const { deleteOrganization } = useOrganizationDetail(organizationId);
const deleteAction = useCallback(() => {
Modal.error({
title: '确定删除吗?',
content: <Text></Text>,
onOk: () => {
deleteOrganization().then(() => {
onDelete
? onDelete()
: Router.push({
pathname: `/`,
});
});
},
okButtonProps: {
type: 'danger',
},
style: { maxWidth: '96vw' },
});
}, [deleteOrganization, onDelete]);
return (
<Text type="danger" onClick={deleteAction}>
{children || (
<Space>
<IconDelete />
</Space>
)}
</Text>
);
};

View File

@ -1,26 +1,115 @@
import { IconSmallTriangleDown } from '@douyinfe/semi-icons'; import { IconAppCenter, IconApps, IconSmallTriangleDown } from '@douyinfe/semi-icons';
import { Avatar, Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui'; import { Avatar, Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render'; import { DataRender } from 'components/data-render';
import { LogoImage, LogoText } from 'components/logo'; import { LogoImage, LogoText } from 'components/logo';
import { useUserOrganizations } from 'data/organization'; import { useUserOrganizations } from 'data/organization';
import { useUser } from 'data/user';
import Link from 'next/link'; import Link from 'next/link';
import Router from 'next/router';
import { useCallback } from 'react';
import styles from './index.module.scss'; import styles from './index.module.scss';
const { Text, Paragraph } = Typography; const { Text, Paragraph } = Typography;
export const OrganizationPublicSwitcher = () => { const Inner = () => {
const { const {
data: userOrganizations, data: userOrganizations,
loading: userOrganizationsLoading, loading: userOrganizationsLoading,
error: userOrganizationsError, error: userOrganizationsError,
} = useUserOrganizations(); } = useUserOrganizations();
const gotoCreate = useCallback(() => { return (
Router.push(`/app/org/create`); <Dropdown
}, []); trigger="click"
render={
<DataRender
loading={userOrganizationsLoading}
error={userOrganizationsError}
normalContent={() => {
return (
<Dropdown.Menu>
{userOrganizations.length ? (
<>
{userOrganizations.map((org) => {
return (
<Dropdown.Item key={org.id}>
<Link
href={{
pathname: '/app/org/[organizationId]',
query: {
organizationId: org.id,
},
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Avatar size="extra-small" src={org.logo} style={{ marginRight: 8 }} />
<Paragraph
style={{
maxWidth: 100,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{org.name}
</Paragraph>
</a>
</Link>
</Dropdown.Item>
);
})}
<Dropdown.Divider />
</>
) : null}
<Dropdown.Item>
<Link
href={{
pathname: '/',
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Text>
<Space>
<Avatar size="extra-small">
<IconApps />
</Avatar>
广
</Space>
</Text>
</a>
</Link>
</Dropdown.Item>
<Dropdown.Item>
<Link
href={{
pathname: '/app/org/create',
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Text>
<Space>
<Avatar size="extra-small">
<IconAppCenter />
</Avatar>
</Space>
</Text>
</a>
</Link>
</Dropdown.Item>
</Dropdown.Menu>
);
}}
/>
}
>
<Button size="small" icon={<IconSmallTriangleDown />} style={{ marginLeft: 12 }} />
</Dropdown>
);
};
export const OrganizationPublicSwitcher = () => {
const { user } = useUser();
return ( return (
<span className={styles.nameWrap}> <span className={styles.nameWrap}>
@ -28,57 +117,7 @@ export const OrganizationPublicSwitcher = () => {
<LogoImage /> <LogoImage />
<LogoText /> <LogoText />
</Space> </Space>
<Dropdown {user && <Inner />}
trigger="click"
render={
<DataRender
loading={userOrganizationsLoading}
error={userOrganizationsError}
normalContent={() => {
return (
<Dropdown.Menu>
{(userOrganizations || []).map((org) => {
return (
<Dropdown.Item key={org.id}>
<Link
href={{
pathname: '/app/org/[organizationId]',
query: {
organizationId: org.id,
},
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Avatar size="extra-small" src={org.logo} style={{ marginRight: 8 }} />
<Paragraph
style={{
maxWidth: 100,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{org.name}
</Paragraph>
</a>
</Link>
</Dropdown.Item>
);
})}
<Dropdown.Divider />
<Dropdown.Item onClick={gotoCreate}>
<Text>
<Space></Space>
</Text>
</Dropdown.Item>
</Dropdown.Menu>
);
}}
/>
}
>
<Button size="small" icon={<IconSmallTriangleDown />} style={{ marginLeft: 12 }} />
</Dropdown>
</span> </span>
); );
}; };

View File

@ -36,29 +36,9 @@ export const OrganizationSetting: React.FC<IProps> = ({ organizationId, tab, onN
<OrganizationMembers organizationId={organizationId} /> <OrganizationMembers organizationId={organizationId} />
</TabPane> </TabPane>
{/* <TabPane tab={TitleMap['base']} itemKey="base">
<Base wiki={data} update={update as any} />
</TabPane>
<TabPane tab={TitleMap['users']} itemKey="users">
<Users wikiId={wikiId} />
</TabPane>
<TabPane tab={TitleMap['tocs']} itemKey="tocs">
<WikiTocsManager wikiId={wikiId} />
</TabPane>
<TabPane tab={TitleMap['privacy']} itemKey="privacy">
<Privacy wikiId={wikiId} />
</TabPane>
<TabPane tab={TitleMap['import']} itemKey="import">
<Import wikiId={wikiId} />
</TabPane>
<TabPane tab={TitleMap['more']} itemKey="more"> <TabPane tab={TitleMap['more']} itemKey="more">
<More wikiId={wikiId} /> <More organizationId={organizationId} />
</TabPane> */} </TabPane>
</Tabs> </Tabs>
</> </>
); );

View File

@ -1,62 +0,0 @@
import { Banner, Button, Input, Modal, Select, Space } from '@douyinfe/semi-ui';
import { AuthEnum, AuthEnumArray } from '@think/domains';
import React, { useCallback, useState } from 'react';
interface IProps {
visible: boolean;
toggleVisible: (arg) => void;
onOk: (arg) => any;
}
export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const [userName, setUserName] = useState('');
const handleOk = useCallback(() => {
onOk({ userName, userAuth }).then(() => {
setUserAuth(AuthEnum.noAccess);
setUserName('');
toggleVisible(false);
});
}, [onOk, userName, userAuth, toggleVisible]);
return (
<Modal
title={'添加成员'}
okText={'邀请对方'}
visible={visible}
onOk={handleOk}
onCancel={() => toggleVisible(false)}
maskClosable={false}
style={{ maxWidth: '96vw' }}
footer={null}
>
<div style={{ marginTop: 16 }}>
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
<Space>
<Select value={userAuth} onChange={setUserAuth} style={{ width: 120 }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
<Input
autofocus
placeholder="输入对方用户名"
value={userName}
onChange={setUserName}
style={{ width: 270 }}
></Input>
</Space>
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk} disabled={!userName}>
</Button>
</div>
</Modal>
);
};

View File

@ -1,58 +0,0 @@
import { Banner, Button, Modal, Select } from '@douyinfe/semi-ui';
import { AuthEnum, AuthEnumArray, IAuth, IUser } from '@think/domains';
import React, { useCallback, useEffect, useState } from 'react';
interface IProps {
visible: boolean;
toggleVisible: (arg) => void;
currentUser: { user: IUser; auth: IAuth };
onOk: (arg) => any;
}
export const EditUser: React.FC<IProps> = ({ visible, toggleVisible, currentUser, onOk }) => {
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const handleOk = useCallback(() => {
onOk(userAuth).then(() => {
setUserAuth(AuthEnum.noAccess);
toggleVisible(false);
});
}, [onOk, userAuth, toggleVisible]);
useEffect(() => {
if (!visible) {
setUserAuth(AuthEnum.noAccess);
}
}, [visible]);
return (
<Modal
title={`修改用户${currentUser && currentUser.user.name}权限`}
visible={visible}
onOk={handleOk}
onCancel={() => toggleVisible(false)}
maskClosable={false}
style={{ maxWidth: '96vw' }}
footer={null}
>
<div style={{ marginTop: 16 }}>
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
{}
<Select value={userAuth} onChange={setUserAuth} style={{ width: '100%' }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk}>
</Button>
</div>
</Modal>
);
};

View File

@ -1,6 +0,0 @@
.wrap {
> header {
display: flex;
justify-content: flex-end;
}
}

View File

@ -1,110 +1,12 @@
import { IconDelete, IconEdit } from '@douyinfe/semi-icons'; import { IOrganization } from '@think/domains';
import { Banner, Button, Popconfirm, Table, Typography } from '@douyinfe/semi-ui'; import { Members } from 'components/members';
import { AuthEnumTextMap, IOrganization } from '@think/domains';
import { DataRender } from 'components/data-render';
import { LocaleTime } from 'components/locale-time';
import { useOrganizationMembers } from 'data/organization'; import { useOrganizationMembers } from 'data/organization';
import { useToggle } from 'hooks/use-toggle'; import React from 'react';
import React, { useState } from 'react';
import { AddUser } from './add';
import { EditUser } from './edit';
import styles from './index.module.scss';
interface IProps { interface IProps {
organizationId: IOrganization['id']; organizationId: IOrganization['id'];
} }
const { Title, Paragraph } = Typography;
const { Column } = Table;
export const OrganizationMembers: React.FC<IProps> = ({ organizationId }) => { export const OrganizationMembers: React.FC<IProps> = ({ organizationId }) => {
const { data, loading, error, refresh, addUser, updateUser, deleteUser } = useOrganizationMembers(organizationId); return <Members id={organizationId} hook={useOrganizationMembers} />;
const [visible, toggleVisible] = useToggle(false);
const [editVisible, toggleEditVisible] = useToggle(false);
const [currentUser, setCurrentUser] = useState(null);
const editUser = (user) => {
setCurrentUser(user);
toggleEditVisible(true);
};
const handleEdit = (userAuth) => {
return updateUser({ userName: currentUser.user.name, userAuth }).then(() => {
setCurrentUser(null);
});
};
return (
<div className={styles.wrap}>
<header>{/* <MemberAdder /> */}</header>
<DataRender
loading={loading}
error={error}
normalContent={() => (
<div>
<Banner
fullMode={false}
type="info"
bordered
icon={null}
style={{ margin: '16px 0' }}
title={<Title heading={6}></Title>}
description={
<div>
<Paragraph></Paragraph>
<Paragraph></Paragraph>
<Paragraph>访</Paragraph>
</div>
}
/>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button theme="solid" onClick={toggleVisible}>
</Button>
</div>
<Table style={{ margin: '16px 0' }} dataSource={data.data} size="small" pagination={false}>
<Column title="用户名" dataIndex="user.name" key="user.name" />
<Column
title="成员权限"
dataIndex="auth.auth"
key="auth.auth"
align="center"
render={(auth) => AuthEnumTextMap[auth]}
/>
<Column
title="加入时间"
dataIndex="auth.createdAt"
key="auth.createdAt"
align="center"
render={(d) => <LocaleTime date={d} />}
/>
<Column
title="操作"
dataIndex="operate"
key="operate"
align="center"
render={(_, data) => (
<>
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={() => editUser(data)} />
<Popconfirm
showArrow
title="确认删除该成员?"
onConfirm={() => deleteUser({ userName: data.user.name, userAuth: data.auth.auth })}
>
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
</Popconfirm>
</>
)}
/>
</Table>
</div>
)}
/>
<AddUser visible={visible} toggleVisible={toggleVisible} onOk={addUser} />
<EditUser visible={editVisible} toggleVisible={toggleEditVisible} currentUser={currentUser} onOk={handleEdit} />
</div>
);
}; };

View File

@ -1,27 +1,23 @@
import { Banner, Button, Typography } from '@douyinfe/semi-ui'; import { Banner, Button, Typography } from '@douyinfe/semi-ui';
import { WorkspaceDeletor } from 'components/wiki/delete'; import { OrganizationDeletor } from 'components/organization/delete';
interface IProps {
wikiId: string;
}
const { Paragraph } = Typography; const { Paragraph } = Typography;
export const More: React.FC<IProps> = ({ wikiId }) => { export const More = ({ organizationId }) => {
return ( return (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<Banner <Banner
fullMode={false} fullMode={false}
type="danger" type="danger"
closeIcon={null} closeIcon={null}
description={<Paragraph></Paragraph>} description={<Paragraph></Paragraph>}
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
/> />
<WorkspaceDeletor wikiId={wikiId}> <OrganizationDeletor organizationId={organizationId}>
<Button type="danger" theme="solid"> <Button type="danger" theme="solid">
</Button> </Button>
</WorkspaceDeletor> </OrganizationDeletor>
</div> </div>
); );
}; };

View File

@ -1,12 +1,10 @@
import { IconAppCenter, IconSmallTriangleDown } from '@douyinfe/semi-icons'; import { IconAppCenter, IconApps, IconSmallTriangleDown } from '@douyinfe/semi-icons';
import { Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui'; import { Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui';
import { Avatar } from '@douyinfe/semi-ui'; import { Avatar } from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render'; import { DataRender } from 'components/data-render';
import { useOrganizationDetail, useUserOrganizations } from 'data/organization'; import { useOrganizationDetail, useUserOrganizations } from 'data/organization';
import { useRouterQuery } from 'hooks/use-router-query'; import { useRouterQuery } from 'hooks/use-router-query';
import Link from 'next/link'; import Link from 'next/link';
import Router from 'next/router';
import { useCallback } from 'react';
import styles from './index.module.scss'; import styles from './index.module.scss';
@ -22,10 +20,6 @@ export const OrganizationSwitcher = () => {
error: userOrganizationsError, error: userOrganizationsError,
} = useUserOrganizations(); } = useUserOrganizations();
const gotoCreate = useCallback(() => {
Router.push(`/app/org/create`);
}, []);
return ( return (
<DataRender <DataRender
loading={loading} loading={loading}
@ -64,44 +58,75 @@ export const OrganizationSwitcher = () => {
normalContent={() => { normalContent={() => {
return ( return (
<Dropdown.Menu> <Dropdown.Menu>
{(userOrganizations || []).map((org) => { {userOrganizations.length ? (
return ( <>
<Dropdown.Item key={org.id}> {userOrganizations.map((org) => {
<Link return (
href={{ <Dropdown.Item key={org.id}>
pathname: '/app/org/[organizationId]', <Link
query: { href={{
organizationId: org.id, pathname: '/app/org/[organizationId]',
}, query: {
}} organizationId: org.id,
> },
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Avatar size="extra-small" src={org.logo} style={{ marginRight: 8 }} />
<Paragraph
style={{
maxWidth: 100,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}} }}
> >
{org.name} <a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
</Paragraph> <Avatar size="extra-small" src={org.logo} style={{ marginRight: 8 }} />
</a> <Paragraph
</Link> style={{
</Dropdown.Item> maxWidth: 100,
); whiteSpace: 'nowrap',
})} textOverflow: 'ellipsis',
<Dropdown.Divider /> overflow: 'hidden',
<Dropdown.Item onClick={gotoCreate}> }}
<Text> >
<Space> {org.name}
<Avatar size="extra-small"> </Paragraph>
<IconAppCenter /> </a>
</Avatar> </Link>
</Dropdown.Item>
</Space> );
</Text> })}
<Dropdown.Divider />
</>
) : null}
<Dropdown.Item>
<Link
href={{
pathname: '/',
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Text>
<Space>
<Avatar size="extra-small">
<IconApps />
</Avatar>
广
</Space>
</Text>
</a>
</Link>
</Dropdown.Item>
<Dropdown.Item>
<Link
href={{
pathname: '/app/org/create',
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Text>
<Space>
<Avatar size="extra-small">
<IconAppCenter />
</Avatar>
</Space>
</Text>
</a>
</Link>
</Dropdown.Item> </Dropdown.Item>
</Dropdown.Menu> </Dropdown.Menu>
); );

View File

@ -27,6 +27,7 @@ export const WikiCreator: React.FC<IProps> = ({ visible, toggleVisible }) => {
}); });
}); });
}; };
const handleCancel = () => { const handleCancel = () => {
toggleVisible(false); toggleVisible(false);
}; };

View File

@ -1,6 +1,7 @@
import { IconDelete } from '@douyinfe/semi-icons'; import { IconDelete } from '@douyinfe/semi-icons';
import { Modal, Space, Typography } from '@douyinfe/semi-ui'; import { Modal, Space, Typography } from '@douyinfe/semi-ui';
import { useOwnWikis } from 'data/wiki'; import { useOwnWikis } from 'data/wiki';
import { useRouterQuery } from 'hooks/use-router-query';
import Router from 'next/router'; import Router from 'next/router';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
@ -11,8 +12,9 @@ interface IProps {
const { Text } = Typography; const { Text } = Typography;
export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children }) => { export const WikiDeletor: React.FC<IProps> = ({ wikiId, onDelete, children }) => {
const { deletWiki } = useOwnWikis(); const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { deletWiki } = useOwnWikis(organizationId);
const deleteAction = useCallback(() => { const deleteAction = useCallback(() => {
Modal.error({ Modal.error({
@ -23,7 +25,8 @@ export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children
onDelete onDelete
? onDelete() ? onDelete()
: Router.push({ : Router.push({
pathname: `/wiki`, pathname: `/app/org/[organizationId]`,
query: { organizationId },
}); });
}); });
}, },
@ -32,7 +35,7 @@ export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children
}, },
style: { maxWidth: '96vw' }, style: { maxWidth: '96vw' },
}); });
}, [wikiId, deletWiki, onDelete]); }, [organizationId, wikiId, deletWiki, onDelete]);
return ( return (
<Text type="danger" onClick={deleteAction}> <Text type="danger" onClick={deleteAction}>

View File

@ -1,5 +1,5 @@
import { Banner, Button, Typography } from '@douyinfe/semi-ui'; import { Banner, Button, Typography } from '@douyinfe/semi-ui';
import { WorkspaceDeletor } from 'components/wiki/delete'; import { WikiDeletor } from 'components/wiki/delete';
interface IProps { interface IProps {
wikiId: string; wikiId: string;
@ -17,11 +17,11 @@ export const More: React.FC<IProps> = ({ wikiId }) => {
description={<Paragraph></Paragraph>} description={<Paragraph></Paragraph>}
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
/> />
<WorkspaceDeletor wikiId={wikiId}> <WikiDeletor wikiId={wikiId}>
<Button type="danger" theme="solid"> <Button type="danger" theme="solid">
</Button> </Button>
</WorkspaceDeletor> </WikiDeletor>
</div> </div>
); );
}; };

View File

@ -7,5 +7,11 @@ interface IProps {
} }
export const Users: React.FC<IProps> = ({ wikiId }) => { export const Users: React.FC<IProps> = ({ wikiId }) => {
return <Members id={wikiId} hook={useWikiMembers} />; return (
<Members
id={wikiId}
hook={useWikiMembers}
descriptions={['权限继承:默认继承组织成员权限', '超级管理员:组织超级管理员和知识库创建者']}
/>
);
}; };

View File

@ -65,11 +65,20 @@ export type DocAuth = {
* @param cookie * @param cookie
* @returns * @returns
*/ */
export const getDocumentMembers = (documentId, cookie = null): Promise<Array<{ user: IUser; auth: IAuthority }>> => { export const getDocumentMembers = (
documentId,
page,
pageSize,
cookie = null
): Promise<Array<{ user: IUser; auth: IAuthority }>> => {
return HttpClient.request({ return HttpClient.request({
method: DocumentApiDefinition.getMemberById.method, method: DocumentApiDefinition.getMemberById.method,
url: DocumentApiDefinition.getMemberById.client(documentId), url: DocumentApiDefinition.getMemberById.client(documentId),
cookie, cookie,
params: {
page,
pageSize,
},
}); });
}; };
@ -79,9 +88,11 @@ export const getDocumentMembers = (documentId, cookie = null): Promise<Array<{ u
* @returns * @returns
*/ */
export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{ user: IUser; auth: IAuthority }>>) => { export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{ user: IUser; auth: IAuthority }>>) => {
const [pageSize] = useState(12);
const [page, setPage] = useState(1);
const { data, error, isLoading, refetch } = useQuery( const { data, error, isLoading, refetch } = useQuery(
DocumentApiDefinition.getMemberById.client(documentId), [DocumentApiDefinition.getMemberById.client(documentId), page],
() => getDocumentMembers(documentId), () => getDocumentMembers(documentId, page, pageSize),
options options
); );
@ -124,7 +135,7 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
[refetch, documentId] [refetch, documentId]
); );
return { data, loading: isLoading, error, addUser, updateUser, deleteUser }; return { data, loading: isLoading, error, page, pageSize, setPage, addUser, updateUser, deleteUser };
}; };
/** /**

View File

@ -108,17 +108,32 @@ export const useOrganizationDetail = (id) => {
[refetch, id] [refetch, id]
); );
return { data, error, loading: isLoading, refresh: refetch, update }; const deleteOrganization = useCallback(async () => {
const res = await HttpClient.request({
method: OrganizationApiDefinition.deleteOrganization.method,
url: OrganizationApiDefinition.deleteOrganization.client(id),
});
refetch();
return res;
}, [refetch, id]);
return { data, error, loading: isLoading, refresh: refetch, update, deleteOrganization };
}; };
export const getOrganizationMembers = ( export const getOrganizationMembers = (
id, id,
page = 1,
pageSize,
cookie = null cookie = null
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => { ): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
return HttpClient.request({ return HttpClient.request({
method: OrganizationApiDefinition.getMembers.method, method: OrganizationApiDefinition.getMembers.method,
url: OrganizationApiDefinition.getMembers.client(id), url: OrganizationApiDefinition.getMembers.client(id),
cookie, cookie,
params: {
page,
pageSize,
},
}); });
}; };
@ -127,8 +142,10 @@ export const getOrganizationMembers = (
* @returns * @returns
*/ */
export const useOrganizationMembers = (id) => { export const useOrganizationMembers = (id) => {
const { data, error, isLoading, refetch } = useQuery(OrganizationApiDefinition.getMembers.client(id), () => const [pageSize] = useState(12);
getOrganizationMembers(id) const [page, setPage] = useState(1);
const { data, error, isLoading, refetch } = useQuery([OrganizationApiDefinition.getMembers.client(id), page], () =>
getOrganizationMembers(id, page, pageSize)
); );
const addUser = useCallback( const addUser = useCallback(
@ -170,5 +187,16 @@ export const useOrganizationMembers = (id) => {
[refetch, id] [refetch, id]
); );
return { data, error, loading: isLoading, refresh: refetch, addUser, updateUser, deleteUser }; return {
data,
error,
loading: isLoading,
page,
pageSize,
setPage,
refresh: refetch,
addUser,
updateUser,
deleteUser,
};
}; };

View File

@ -1,5 +1,5 @@
import { Toast } from '@douyinfe/semi-ui'; import { Toast } from '@douyinfe/semi-ui';
import { ILoginUser, ISystemConfig, IUser, UserApiDefinition } from '@think/domains'; import { ILoginUser, ISystemConfig, IUser, SystemApiDefinition, UserApiDefinition } from '@think/domains';
import { getStorage, setStorage } from 'helpers/storage'; import { getStorage, setStorage } from 'helpers/storage';
import { useAsyncLoading } from 'hooks/use-async-loading'; import { useAsyncLoading } from 'hooks/use-async-loading';
import Router, { useRouter } from 'next/router'; import Router, { useRouter } from 'next/router';
@ -138,10 +138,17 @@ export const useUser = () => {
}; };
}; };
/** export const useSystemPublicConfig = () => {
* const { data, error, isLoading, refetch } = useQuery(SystemApiDefinition.getPublicConfig.client(), () =>
* @returns HttpClient.request<ISystemConfig>({
*/ method: SystemApiDefinition.getPublicConfig.method,
url: SystemApiDefinition.getPublicConfig.client(),
})
);
return { data, error, loading: isLoading, refresh: refetch };
};
export const useSystemConfig = () => { export const useSystemConfig = () => {
const { data, error, isLoading, refetch } = useQuery(UserApiDefinition.getSystemConfig.client(), () => const { data, error, isLoading, refetch } = useQuery(UserApiDefinition.getSystemConfig.client(), () =>
HttpClient.request<ISystemConfig>({ HttpClient.request<ISystemConfig>({

View File

@ -1,4 +1,4 @@
import { IAuth, IDocument, IUser, IWiki, IWikiUser, WikiApiDefinition } from '@think/domains'; import { IAuth, IDocument, IUser, IWiki, WikiApiDefinition } from '@think/domains';
import { event, REFRESH_TOCS } from 'event'; import { event, REFRESH_TOCS } from 'event';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
@ -97,7 +97,7 @@ export const useOwnWikis = (organizationId) => {
); );
/** /**
* *
* @param id * @param id
* @returns * @returns
*/ */
@ -318,12 +318,18 @@ export const useWikiDocuments = (wikiId) => {
*/ */
export const getWikiMembers = ( export const getWikiMembers = (
wikiId, wikiId,
page,
pageSize,
cookie = null cookie = null
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => { ): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
return HttpClient.request({ return HttpClient.request({
method: WikiApiDefinition.getMemberById.method, method: WikiApiDefinition.getMemberById.method,
url: WikiApiDefinition.getMemberById.client(wikiId), url: WikiApiDefinition.getMemberById.client(wikiId),
cookie, cookie,
params: {
page,
pageSize,
},
}); });
}; };
@ -333,8 +339,10 @@ export const getWikiMembers = (
* @returns * @returns
*/ */
export const useWikiMembers = (wikiId) => { export const useWikiMembers = (wikiId) => {
const { data, error, isLoading, refetch } = useQuery(WikiApiDefinition.getMemberById.client(wikiId), () => const [pageSize] = useState(12);
getWikiMembers(wikiId) const [page, setPage] = useState(1);
const { data, error, isLoading, refetch } = useQuery([WikiApiDefinition.getMemberById.client(wikiId), page], () =>
getWikiMembers(wikiId, page, pageSize)
); );
const addUser = useCallback( const addUser = useCallback(
@ -376,7 +384,7 @@ export const useWikiMembers = (wikiId) => {
[refetch, wikiId] [refetch, wikiId]
); );
return { data, loading: isLoading, error, addUser, updateUser, deleteUser }; return { data, loading: isLoading, error, page, pageSize, setPage, addUser, updateUser, deleteUser };
}; };
/** /**

View File

@ -1,11 +1,12 @@
import { Layout as SemiLayout, Nav, Space } from '@douyinfe/semi-ui'; import { Button, Layout as SemiLayout, Nav, Space } from '@douyinfe/semi-ui';
import { Message } from 'components/message'; import { Message } from 'components/message';
import { OrganizationPublicSwitcher } from 'components/organization/public-switcher'; import { OrganizationPublicSwitcher } from 'components/organization/public-switcher';
import { Theme } from 'components/theme'; import { Theme } from 'components/theme';
import { User } from 'components/user'; import { User } from 'components/user';
import { useUser } from 'data/user';
import { IsOnMobile } from 'hooks/use-on-mobile'; import { IsOnMobile } from 'hooks/use-on-mobile';
import Router, { useRouter } from 'next/router'; import Router, { useRouter } from 'next/router';
import React from 'react'; import React, { useCallback } from 'react';
const { Header: SemiHeader } = SemiLayout; const { Header: SemiHeader } = SemiLayout;
@ -28,12 +29,26 @@ const menus = [
}); });
}, },
}, },
{
itemKey: '/template',
text: '模板',
onClick: () => {
Router.push({
pathname: `/template`,
});
},
},
]; ];
export const RouterHeader: React.FC = () => { export const RouterHeader: React.FC = () => {
const { user } = useUser();
const { pathname } = useRouter(); const { pathname } = useRouter();
const { isMobile } = IsOnMobile.useHook(); const { isMobile } = IsOnMobile.useHook();
const gotoApp = useCallback(() => {
Router.push(`/app`);
}, []);
return ( return (
<SemiHeader> <SemiHeader>
{isMobile ? ( {isMobile ? (
@ -47,6 +62,11 @@ export const RouterHeader: React.FC = () => {
} }
footer={ footer={
<Space> <Space>
{user && (
<Button theme="solid" onClick={gotoApp}>
</Button>
)}
<Theme /> <Theme />
<User /> <User />
</Space> </Space>
@ -65,7 +85,12 @@ export const RouterHeader: React.FC = () => {
items={menus} items={menus}
footer={ footer={
<Space> <Space>
<Message /> {user && (
<Button theme="solid" onClick={gotoApp}>
</Button>
)}
{user && <Message />}
<Theme /> <Theme />
<User /> <User />
</Space> </Space>

View File

@ -1,4 +1,4 @@
import { Typography } from '@douyinfe/semi-ui'; import { Banner, Typography } from '@douyinfe/semi-ui';
import { SystemConfig } from 'components/admin/system-config'; import { SystemConfig } from 'components/admin/system-config';
import { Seo } from 'components/seo'; import { Seo } from 'components/seo';
import { useUser } from 'data/user'; import { useUser } from 'data/user';
@ -37,6 +37,7 @@ const Page: NextPage = () => {
</Title> </Title>
</div> </div>
<Banner type="info" description="该部分是全局的系统管理后台,用于系统配置管理等操作!" />
<SystemConfig tab={tab} onNavigate={navigate} /> <SystemConfig tab={tab} onNavigate={navigate} />
</> </>
) : ( ) : (

View File

@ -1,32 +1,103 @@
import { Spin } from '@douyinfe/semi-ui'; import { Avatar, Button, Table, Typography } from '@douyinfe/semi-ui';
import { usePeronalOrganization } from 'data/organization'; import { IOrganization } from '@think/domains';
import { DataRender } from 'components/data-render';
import { LocaleTime } from 'components/locale-time';
import { usePeronalOrganization, useUserOrganizations } from 'data/organization';
import { SingleColumnLayout } from 'layouts/single-column'; import { SingleColumnLayout } from 'layouts/single-column';
import Link from 'next/link';
import Router from 'next/router'; import Router from 'next/router';
import { useEffect } from 'react'; import { useCallback, useEffect } from 'react';
const { Title, Paragraph } = Typography;
const { Column } = Table;
const Page = () => { const Page = () => {
const { data: organization } = usePeronalOrganization(); const { data: organization } = usePeronalOrganization();
const {
data: userOrganizations,
loading: userOrganizationsLoading,
error: userOrganizationsError,
} = useUserOrganizations();
const gotoCreate = useCallback(() => {
Router.push({
pathname: '/app/org/create',
});
}, []);
useEffect(() => { useEffect(() => {
if (userOrganizations && userOrganizations.length) return;
if (!organization) return; if (!organization) return;
Router.replace({ Router.replace({
pathname: `/app/org/[organizationId]`, pathname: `/app/org/[organizationId]`,
query: { organizationId: organization.id }, query: { organizationId: organization.id },
}); });
}, [organization]); }, [organization, userOrganizations]);
return ( return (
<SingleColumnLayout> <SingleColumnLayout>
<div className="container"> <div className="container">
<div <div style={{ marginBottom: 24, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
style={{ <Title heading={3} style={{ margin: '8px 0' }}>
padding: '10vh',
textAlign: 'center', </Title>
}} <Button theme="solid" onClick={gotoCreate}>
>
<Spin></Spin> </Button>
</div> </div>
<DataRender
loading={userOrganizationsLoading}
error={userOrganizationsError}
normalContent={() => (
<>
<Table style={{ margin: '16px 0' }} dataSource={userOrganizations} size="small" pagination={false}>
<Column
title="名称"
dataIndex="name"
key="name"
width={200}
render={(_, org: IOrganization) => {
return (
<Link
href={{
pathname: `/app/org/[organizationId]`,
query: {
organizationId: org.id,
},
}}
>
<a style={{ color: 'inherit', textDecoration: 'none' }}>
<span style={{ display: 'flex', alignItems: 'center' }}>
<Avatar size="small" src={org.logo} style={{ marginRight: 8 }} />
<Paragraph
style={{
maxWidth: 100,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
strong
>
{org.name}
</Paragraph>
</span>
</a>
</Link>
);
}}
/>
<Column
width={120}
title="创建时间"
dataIndex="createdAt"
key="createdAt"
render={(date) => <LocaleTime date={date} />}
/>
</Table>
</>
)}
/>
</div> </div>
</SingleColumnLayout> </SingleColumnLayout>
); );

View File

@ -1,12 +1,3 @@
.wikiItemWrap { .wrap {
padding: 12px 16px !important;
margin: 8px 2px;
cursor: pointer;
background-color: var(--semi-color-bg-2);
border: 1px solid var(--semi-color-border) !important;
}
.titleWrap {
display: flex; display: flex;
justify-content: space-between;
} }

View File

@ -1,35 +1,51 @@
import { Button, Typography } from '@douyinfe/semi-ui'; import { Button, Typography } from '@douyinfe/semi-ui';
import { Seo } from 'components/seo'; import { Seo } from 'components/seo';
import { toLogin, useUser } from 'data/user';
import { SingleColumnLayout } from 'layouts/single-column'; import { SingleColumnLayout } from 'layouts/single-column';
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import Router from 'next/router'; import Router from 'next/router';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
const { Title } = Typography; import styles from './index.module.scss';
const { Title, Paragraph } = Typography;
const Page: NextPage = () => { const Page: NextPage = () => {
const gotoApp = useCallback(() => { const { user } = useUser();
Router.push(`/app`);
const start = useCallback(() => {
if (user) {
Router.push(`/app`);
} else {
toLogin();
}
}, [user]);
const toGithub = useCallback(() => {
window.open('https://github.com/fantasticit/think');
}, []); }, []);
return ( return (
<SingleColumnLayout> <SingleColumnLayout>
<Seo title="主页" /> <Seo title="主页" />
<div className="container"> <div className="container">
<div style={{ marginBottom: 24 }}> <div className={styles.wrap}>
<Title heading={3} style={{ margin: '8px 0' }}> <div>
<div style={{ textAlign: 'center' }}>
</Title> <Title style={{ margin: 24 }}></Title>
</div> <Paragraph type="tertiary">
<div 线
style={{ </Paragraph>
padding: '10vh', </div>
textAlign: 'center', <div style={{ margin: '48px 0', textAlign: 'center' }}>
}} <Button theme="solid" onClick={start}>
> 使
<Button theme="solid" onClick={gotoApp}> </Button>
<Button style={{ marginLeft: 12 }} onClick={toGithub}>
</Button> Github
</Button>
</div>
</div>
</div> </div>
</div> </div>
</SingleColumnLayout> </SingleColumnLayout>

View File

@ -2,7 +2,7 @@ import { Button, Col, Form, Layout, Modal, Row, Space, Toast, Typography } from
import { Author } from 'components/author'; import { Author } from 'components/author';
import { LogoImage, LogoText } from 'components/logo'; import { LogoImage, LogoText } from 'components/logo';
import { Seo } from 'components/seo'; import { Seo } from 'components/seo';
import { useRegister, useVerifyCode } from 'data/user'; import { useRegister, useSystemPublicConfig, useVerifyCode } from 'data/user';
import { isEmail } from 'helpers/validator'; import { isEmail } from 'helpers/validator';
import { useInterval } from 'hooks/use-interval'; import { useInterval } from 'hooks/use-interval';
import { useRouterQuery } from 'hooks/use-router-query'; import { useRouterQuery } from 'hooks/use-router-query';
@ -24,6 +24,7 @@ const Page = () => {
const [hasSendVerifyCode, toggleHasSendVerifyCode] = useToggle(false); const [hasSendVerifyCode, toggleHasSendVerifyCode] = useToggle(false);
const [countDown, setCountDown] = useState(0); const [countDown, setCountDown] = useState(0);
const { register, loading } = useRegister(); const { register, loading } = useRegister();
const { data: systemConfig } = useSystemPublicConfig();
const { sendVerifyCode, loading: sendVerifyCodeLoading } = useVerifyCode(); const { sendVerifyCode, loading: sendVerifyCodeLoading } = useVerifyCode();
const onFormChange = useCallback((formState) => { const onFormChange = useCallback((formState) => {
@ -133,22 +134,29 @@ const Page = () => {
]} ]}
/> />
<Row gutter={8} style={{ paddingTop: 12 }}> {systemConfig && systemConfig.enableEmailVerify ? (
<Col span={16}> <Row gutter={8} style={{ paddingTop: 12 }}>
<Form.Input <Col span={16}>
noLabel <Form.Input
fieldStyle={{ paddingTop: 0 }} noLabel
placeholder={'请输入验证码'} fieldStyle={{ paddingTop: 0 }}
field="verifyCode" placeholder={'请输入验证码'}
rules={[{ required: true, message: '请输入邮箱收到的验证码!' }]} field="verifyCode"
/> rules={[{ required: true, message: '请输入邮箱收到的验证码!' }]}
</Col> />
<Col span={8}> </Col>
<Button disabled={!email || countDown > 0} loading={sendVerifyCodeLoading} onClick={getVerifyCode} block> <Col span={8}>
{hasSendVerifyCode ? countDown : '获取验证码'} <Button
</Button> disabled={!email || countDown > 0}
</Col> loading={sendVerifyCodeLoading}
</Row> onClick={getVerifyCode}
block
>
{hasSendVerifyCode ? countDown : '获取验证码'}
</Button>
</Col>
</Row>
) : null}
<Button htmlType="submit" type="primary" theme="solid" block loading={loading} style={{ margin: '16px 0' }}> <Button htmlType="submit" type="primary" theme="solid" block loading={loading} style={{ margin: '16px 0' }}>

View File

@ -10,7 +10,7 @@ interface AxiosInstance extends Axios {
export const HttpClient = axios.create({ export const HttpClient = axios.create({
baseURL: process.env.SERVER_API_URL, baseURL: process.env.SERVER_API_URL,
timeout: 1 * 3 * 1000, timeout: process.env.NODE_ENV === 'production' ? 10 * 60 * 1000 : 3000,
withCredentials: true, withCredentials: true,
}) as AxiosInstance; }) as AxiosInstance;

View File

@ -7,3 +7,4 @@ export * from './template';
export * from './comment'; export * from './comment';
export * from './star'; export * from './star';
export * from './organization'; export * from './organization';
export * from './system';

View File

@ -19,3 +19,4 @@ __exportStar(require("./template"), exports);
__exportStar(require("./comment"), exports); __exportStar(require("./comment"), exports);
__exportStar(require("./star"), exports); __exportStar(require("./star"), exports);
__exportStar(require("./organization"), exports); __exportStar(require("./organization"), exports);
__exportStar(require("./system"), exports);

View File

@ -40,6 +40,14 @@ export declare const OrganizationApiDefinition: {
server: "/update/:id"; server: "/update/:id";
client: (id: IOrganization['id']) => string; client: (id: IOrganization['id']) => string;
}; };
/**
*
*/
deleteOrganization: {
method: "delete";
server: "/delete/:id";
client: (id: IOrganization['id']) => string;
};
/** /**
* *
*/ */

View File

@ -42,6 +42,14 @@ exports.OrganizationApiDefinition = {
server: '/update/:id', server: '/update/:id',
client: function (id) { return "/organization/update/".concat(id); } client: function (id) { return "/organization/update/".concat(id); }
}, },
/**
*
*/
deleteOrganization: {
method: 'delete',
server: '/delete/:id',
client: function (id) { return "/organization/delete/".concat(id); }
},
/** /**
* *
*/ */

7
packages/domains/lib/api/system.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
export declare const SystemApiDefinition: {
getPublicConfig: {
method: "get";
server: "/";
client: () => string;
};
};

View File

@ -0,0 +1,10 @@
"use strict";
exports.__esModule = true;
exports.SystemApiDefinition = void 0;
exports.SystemApiDefinition = {
getPublicConfig: {
method: 'get',
server: '/',
client: function () { return '/system'; }
}
};

View File

@ -1,33 +1,2 @@
"use strict"; "use strict";
exports.__esModule = true; exports.__esModule = true;
// /**
// * 创建组织数据定义
// */
// export interface CreateOrganizationDto {
// name: string;
// description: string;
// logo: string;
// }
// export enum OrganizationAuthEnum {
// superAdmin = 'superAdmin',
// admin = 'admin',
// member = 'member',
// noAccess = 'noAccess',
// }
// export const AuthEnumTextMap = {
// [OrganizationAuthEnum.superAdmin]: '超级管理员',
// [OrganizationAuthEnum.admin]: '管理员',
// [OrganizationAuthEnum.member]: '成员',
// [OrganizationAuthEnum.noAccess]: '无权限',
// };
// export const OrganizationAuthEnumArray = Object.keys(AuthEnumTextMap).map((value) => ({
// label: AuthEnumTextMap[value],
// value,
// }));
// export interface IOrganizationAuth {
// id: string;
// auth: OrganizationAuthEnum;
// organizationId: IOrganization['id'];
// createdAt: string;
// updatedAt: string;
// }

View File

@ -1,5 +1,6 @@
export interface ISystemConfig { export interface ISystemConfig {
isSystemLocked: boolean; isSystemLocked: boolean;
enableEmailVerify: boolean;
emailServiceHost: string; emailServiceHost: string;
emailServicePassword: string; emailServicePassword: string;
emailServicePort: string; emailServicePort: string;

View File

@ -1,11 +1,3 @@
/**
*
*/
export declare enum UserRole {
normal = "normal",
admin = "admin",
superadmin = "superadmin"
}
/** /**
* *
*/ */
@ -22,7 +14,6 @@ export interface IUser {
password?: string; password?: string;
avatar?: string; avatar?: string;
email?: string; email?: string;
role: UserRole;
status: UserStatus; status: UserStatus;
isSystemAdmin?: boolean; isSystemAdmin?: boolean;
} }

View File

@ -1,15 +1,6 @@
"use strict"; "use strict";
exports.__esModule = true; exports.__esModule = true;
exports.UserStatus = exports.UserRole = void 0; exports.UserStatus = void 0;
/**
*
*/
var UserRole;
(function (UserRole) {
UserRole["normal"] = "normal";
UserRole["admin"] = "admin";
UserRole["superadmin"] = "superadmin";
})(UserRole = exports.UserRole || (exports.UserRole = {}));
/** /**
* *
*/ */

View File

@ -8,21 +8,6 @@ export declare enum WikiStatus {
private = "private", private = "private",
public = "public" public = "public"
} }
/**
*
*/
export declare enum WikiUserStatus {
applying = "applying",
inviting = "inviting",
normal = "normal"
}
/**
*
*/
export declare enum WikiUserRole {
normal = "normal",
admin = "admin"
}
/** /**
* *
*/ */
@ -39,11 +24,3 @@ export interface IWiki {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
/**
*
*/
export interface IWikiUser extends IUser {
userRole: WikiUserRole;
userStatus: WikiUserStatus;
isCreator: boolean;
}

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
exports.__esModule = true; exports.__esModule = true;
exports.WikiUserRole = exports.WikiUserStatus = exports.WikiStatus = void 0; exports.WikiStatus = void 0;
/** /**
* *
*/ */
@ -9,20 +9,3 @@ var WikiStatus;
WikiStatus["private"] = "private"; WikiStatus["private"] = "private";
WikiStatus["public"] = "public"; WikiStatus["public"] = "public";
})(WikiStatus = exports.WikiStatus || (exports.WikiStatus = {})); })(WikiStatus = exports.WikiStatus || (exports.WikiStatus = {}));
/**
*
*/
var WikiUserStatus;
(function (WikiUserStatus) {
WikiUserStatus["applying"] = "applying";
WikiUserStatus["inviting"] = "inviting";
WikiUserStatus["normal"] = "normal";
})(WikiUserStatus = exports.WikiUserStatus || (exports.WikiUserStatus = {}));
/**
*
*/
var WikiUserRole;
(function (WikiUserRole) {
WikiUserRole["normal"] = "normal";
WikiUserRole["admin"] = "admin";
})(WikiUserRole = exports.WikiUserRole || (exports.WikiUserRole = {}));

View File

@ -1,4 +1,4 @@
import { WikiStatus, WikiUserRole, DocumentStatus, IWiki, IDocument } from './models'; import { WikiStatus, DocumentStatus, IWiki, IDocument } from './models';
/** /**
* *
*/ */
@ -32,12 +32,6 @@ export declare const getWikiStatusText: (wiki: IWiki) => string;
* @returns * @returns
*/ */
export declare const isPublicWiki: (currentStatus: IWiki['status']) => boolean; export declare const isPublicWiki: (currentStatus: IWiki['status']) => boolean;
/**
*
* @param role role
* @returns
*/
export declare const getWikiUserRoleText: (role: WikiUserRole) => string;
/** /**
* *
* @param currentStatus document status * @param currentStatus document status

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
exports.__esModule = true; exports.__esModule = true;
exports.isPublicDocument = exports.getWikiUserRoleText = exports.isPublicWiki = exports.getWikiStatusText = exports.DOCUMENT_STATUS = exports.WIKI_USER_ROLES = exports.WIKI_STATUS_LIST = void 0; exports.isPublicDocument = exports.isPublicWiki = exports.getWikiStatusText = exports.DOCUMENT_STATUS = exports.WIKI_USER_ROLES = exports.WIKI_STATUS_LIST = void 0;
var models_1 = require("./models"); var models_1 = require("./models");
/** /**
* *
@ -57,15 +57,6 @@ exports.getWikiStatusText = getWikiStatusText;
*/ */
var isPublicWiki = function (currentStatus) { return currentStatus === models_1.WikiStatus.public; }; var isPublicWiki = function (currentStatus) { return currentStatus === models_1.WikiStatus.public; };
exports.isPublicWiki = isPublicWiki; exports.isPublicWiki = isPublicWiki;
/**
*
* @param role role
* @returns
*/
var getWikiUserRoleText = function (role) {
return exports.WIKI_USER_ROLES.find(function (d) { return d.value === role; }).label;
};
exports.getWikiUserRoleText = getWikiUserRoleText;
/** /**
* *
* @param currentStatus document status * @param currentStatus document status

View File

@ -7,3 +7,4 @@ export * from './template';
export * from './comment'; export * from './comment';
export * from './star'; export * from './star';
export * from './organization'; export * from './organization';
export * from './system';

View File

@ -46,6 +46,15 @@ export const OrganizationApiDefinition = {
client: (id: IOrganization['id']) => `/organization/update/${id}`, client: (id: IOrganization['id']) => `/organization/update/${id}`,
}, },
/**
*
*/
deleteOrganization: {
method: 'delete' as const,
server: '/delete/:id' as const,
client: (id: IOrganization['id']) => `/organization/delete/${id}`,
},
/** /**
* *
*/ */

View File

@ -0,0 +1,7 @@
export const SystemApiDefinition = {
getPublicConfig: {
method: 'get' as const,
server: '/' as const,
client: () => '/system',
},
};

View File

@ -1,5 +1,3 @@
import { IUser } from '../models';
export const UserApiDefinition = { export const UserApiDefinition = {
/** /**
* *

View File

@ -11,39 +11,3 @@ export interface IOrganization {
createUserId: IUser['id']; createUserId: IUser['id'];
isPersonal: boolean; isPersonal: boolean;
} }
// /**
// * 创建组织数据定义
// */
// export interface CreateOrganizationDto {
// name: string;
// description: string;
// logo: string;
// }
// export enum OrganizationAuthEnum {
// superAdmin = 'superAdmin',
// admin = 'admin',
// member = 'member',
// noAccess = 'noAccess',
// }
// export const AuthEnumTextMap = {
// [OrganizationAuthEnum.superAdmin]: '超级管理员',
// [OrganizationAuthEnum.admin]: '管理员',
// [OrganizationAuthEnum.member]: '成员',
// [OrganizationAuthEnum.noAccess]: '无权限',
// };
// export const OrganizationAuthEnumArray = Object.keys(AuthEnumTextMap).map((value) => ({
// label: AuthEnumTextMap[value],
// value,
// }));
// export interface IOrganizationAuth {
// id: string;
// auth: OrganizationAuthEnum;
// organizationId: IOrganization['id'];
// createdAt: string;
// updatedAt: string;
// }

View File

@ -1,5 +1,6 @@
export interface ISystemConfig { export interface ISystemConfig {
isSystemLocked: boolean; isSystemLocked: boolean;
enableEmailVerify: boolean;
emailServiceHost: string; emailServiceHost: string;
emailServicePassword: string; emailServicePassword: string;
emailServicePort: string; emailServicePort: string;

View File

@ -1,12 +1,3 @@
/**
*
*/
export enum UserRole {
normal = 'normal',
admin = 'admin',
superadmin = 'superadmin',
}
/** /**
* *
*/ */
@ -24,7 +15,6 @@ export interface IUser {
password?: string; password?: string;
avatar?: string; avatar?: string;
email?: string; email?: string;
role: UserRole;
status: UserStatus; status: UserStatus;
isSystemAdmin?: boolean; isSystemAdmin?: boolean;
} }

View File

@ -10,23 +10,6 @@ export enum WikiStatus {
public = 'public', public = 'public',
} }
/**
*
*/
export enum WikiUserStatus {
applying = 'applying',
inviting = 'inviting',
normal = 'normal',
}
/**
*
*/
export enum WikiUserRole {
normal = 'normal',
admin = 'admin',
}
/** /**
* *
*/ */
@ -43,12 +26,3 @@ export interface IWiki {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
/**
*
*/
export interface IWikiUser extends IUser {
userRole: WikiUserRole;
userStatus: WikiUserStatus;
isCreator: boolean;
}

View File

@ -1,4 +1,4 @@
import { WikiStatus, WikiUserRole, DocumentStatus, IWiki, IDocument } from './models'; import { WikiStatus, DocumentStatus, IWiki, IDocument } from './models';
/** /**
* *
@ -48,7 +48,7 @@ export const DOCUMENT_STATUS = [
* @returns * @returns
*/ */
export const getWikiStatusText = (wiki: IWiki): string => { export const getWikiStatusText = (wiki: IWiki): string => {
return WIKI_STATUS_LIST.find((t) => t.value === wiki.status).label; return WIKI_STATUS_LIST.find((t) => t.value === wiki.status)!.label;
}; };
/** /**
@ -58,15 +58,6 @@ export const getWikiStatusText = (wiki: IWiki): string => {
*/ */
export const isPublicWiki = (currentStatus: IWiki['status']) => currentStatus === WikiStatus.public; export const isPublicWiki = (currentStatus: IWiki['status']) => currentStatus === WikiStatus.public;
/**
*
* @param role role
* @returns
*/
export const getWikiUserRoleText = (role: WikiUserRole) => {
return WIKI_USER_ROLES.find((d) => d.value === role).label;
};
/** /**
* *
* @param currentStatus document status * @param currentStatus document status

View File

@ -1,18 +1,13 @@
import { AuthEntity } from '@entities/auth.entity'; import { AuthEntity } from '@entities/auth.entity';
import { CommentEntity } from '@entities/comment.entity'; import { CommentEntity } from '@entities/comment.entity';
import { DocumentEntity } from '@entities/document.entity'; import { DocumentEntity } from '@entities/document.entity';
// import { DocumentUserEntity } from '@entities/document-user.entity';
import { MessageEntity } from '@entities/message.entity'; import { MessageEntity } from '@entities/message.entity';
import { OrganizationEntity } from '@entities/organization.entity'; import { OrganizationEntity } from '@entities/organization.entity';
// import { OrganizationUserEntity } from '@entities/organization-user.entity';
import { StarEntity } from '@entities/star.entity'; import { StarEntity } from '@entities/star.entity';
import { SystemEntity } from '@entities/system.entity'; import { SystemEntity } from '@entities/system.entity';
import { TemplateEntity } from '@entities/template.entity'; import { TemplateEntity } from '@entities/template.entity';
import { UserEntity } from '@entities/user.entity'; import { UserEntity } from '@entities/user.entity';
import { VerifyEntity } from '@entities/verify.entity';
import { ViewEntity } from '@entities/view.entity';
import { WikiEntity } from '@entities/wiki.entity'; import { WikiEntity } from '@entities/wiki.entity';
// import { WikiUserEntity } from '@entities/wiki-user.entity';
import { IS_PRODUCTION } from '@helpers/env.helper'; import { IS_PRODUCTION } from '@helpers/env.helper';
import { getLogFileName, ONE_DAY } from '@helpers/log.helper'; import { getLogFileName, ONE_DAY } from '@helpers/log.helper';
import { AuthModule } from '@modules/auth.module'; import { AuthModule } from '@modules/auth.module';
@ -42,17 +37,12 @@ const ENTITIES = [
UserEntity, UserEntity,
AuthEntity, AuthEntity,
OrganizationEntity, OrganizationEntity,
// OrganizationUserEntity,
WikiEntity, WikiEntity,
// WikiUserEntity,
DocumentEntity, DocumentEntity,
// DocumentUserEntity,
StarEntity, StarEntity,
CommentEntity, CommentEntity,
MessageEntity, MessageEntity,
TemplateEntity, TemplateEntity,
ViewEntity,
VerifyEntity,
SystemEntity, SystemEntity,
]; ];

View File

@ -1,4 +1,5 @@
export enum RedisDBEnum { export enum RedisDBEnum {
documentVersion = 0, documentVersion = 0,
view = 1, view = 1,
verify = 2,
} }

View File

@ -1,9 +1,7 @@
import { OperateUserAuthDto } from '@dtos/auth.dto'; import { OperateUserAuthDto } from '@dtos/auth.dto';
import { CreateDocumentDto } from '@dtos/create-document.dto'; import { CreateDocumentDto } from '@dtos/create-document.dto';
import { DocAuthDto } from '@dtos/doc-auth.dto';
import { ShareDocumentDto } from '@dtos/share-document.dto'; import { ShareDocumentDto } from '@dtos/share-document.dto';
import { UpdateDocumentDto } from '@dtos/update-document.dto'; import { UpdateDocumentDto } from '@dtos/update-document.dto';
// import { CheckDocumentAuthority, DocumentAuthorityGuard } from '@guard/document-auth.guard';
import { CheckDocumentStatus, DocumentStatusGuard } from '@guard/document-status.guard'; import { CheckDocumentStatus, DocumentStatusGuard } from '@guard/document-status.guard';
import { JwtGuard } from '@guard/jwt.guard'; import { JwtGuard } from '@guard/jwt.guard';
import { import {
@ -23,10 +21,9 @@ import {
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { DocumentService } from '@services/document.service'; import { DocumentService } from '@services/document.service';
import { DocumentApiDefinition, DocumentStatus } from '@think/domains'; import { DocumentApiDefinition, DocumentStatus, IPagination } from '@think/domains';
@Controller('document') @Controller('document')
// @UseGuards(DocumentAuthorityGuard)
@UseGuards(DocumentStatusGuard) @UseGuards(DocumentStatusGuard)
export class DocumentController { export class DocumentController {
constructor(private readonly documentService: DocumentService) {} constructor(private readonly documentService: DocumentService) {}
@ -78,7 +75,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Get(DocumentApiDefinition.getDetailById.server) @Get(DocumentApiDefinition.getDetailById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('readable')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getDocumentDetail(@Request() req, @Param('id') documentId) { async getDocumentDetail(@Request() req, @Param('id') documentId) {
return await this.documentService.getDocumentDetail(req.user, documentId); return await this.documentService.getDocumentDetail(req.user, documentId);
@ -94,7 +90,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Patch(DocumentApiDefinition.updateById.server) @Patch(DocumentApiDefinition.updateById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('editable')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async updateDocument(@Request() req, @Param('id') documentId, @Body() dto: UpdateDocumentDto) { async updateDocument(@Request() req, @Param('id') documentId, @Body() dto: UpdateDocumentDto) {
return await this.documentService.updateDocument(req.user, documentId, dto); return await this.documentService.updateDocument(req.user, documentId, dto);
@ -109,7 +104,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Get(DocumentApiDefinition.getVersionById.server) @Get(DocumentApiDefinition.getVersionById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('readable')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getDocumentVersion(@Request() req, @Param('id') documentId) { async getDocumentVersion(@Request() req, @Param('id') documentId) {
return await this.documentService.getDocumentVersion(req.user, documentId); return await this.documentService.getDocumentVersion(req.user, documentId);
@ -124,10 +118,9 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Get(DocumentApiDefinition.getMemberById.server) @Get(DocumentApiDefinition.getMemberById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('readable')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getDocUsers(@Request() req, @Param('id') documentId) { async getDocUsers(@Request() req, @Param('id') documentId, @Query() pagination: IPagination) {
return await this.documentService.getDocUsers(req.user, documentId); return await this.documentService.getDocUsers(req.user, documentId, pagination);
} }
/** /**
@ -140,7 +133,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Post(DocumentApiDefinition.addMemberById.server) @Post(DocumentApiDefinition.addMemberById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('createUser')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async addDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) { async addDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) {
return await this.documentService.addDocUser(req.user, documentId, dto); return await this.documentService.addDocUser(req.user, documentId, dto);
@ -156,7 +148,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Patch(DocumentApiDefinition.updateMemberById.server) @Patch(DocumentApiDefinition.updateMemberById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('createUser')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async updateDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) { async updateDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) {
return await this.documentService.updateDocUser(req.user, documentId, dto); return await this.documentService.updateDocUser(req.user, documentId, dto);
@ -172,7 +163,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Post(DocumentApiDefinition.deleteMemberById.server) @Post(DocumentApiDefinition.deleteMemberById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('createUser')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async deleteDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) { async deleteDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) {
return await this.documentService.deleteDocUser(req.user, documentId, dto); return await this.documentService.deleteDocUser(req.user, documentId, dto);
@ -187,7 +177,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Post(DocumentApiDefinition.getChildren.server) @Post(DocumentApiDefinition.getChildren.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('readable')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getChildrenDocuments(@Request() req, @Body() data) { async getChildrenDocuments(@Request() req, @Body() data) {
return await this.documentService.getChildrenDocuments(req.user, data); return await this.documentService.getChildrenDocuments(req.user, data);
@ -202,7 +191,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Delete(DocumentApiDefinition.deleteById.server) @Delete(DocumentApiDefinition.deleteById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('createUser')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async deleteDocument(@Request() req, @Param('id') documentId) { async deleteDocument(@Request() req, @Param('id') documentId) {
return await this.documentService.deleteDocument(req.user, documentId); return await this.documentService.deleteDocument(req.user, documentId);
@ -218,7 +206,6 @@ export class DocumentController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Post(DocumentApiDefinition.shareById.server) @Post(DocumentApiDefinition.shareById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckDocumentAuthority('editable')
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async shareDocument(@Request() req, @Param('id') documentId, @Body() dto: ShareDocumentDto) { async shareDocument(@Request() req, @Param('id') documentId, @Body() dto: ShareDocumentDto) {
return await this.documentService.shareDocument(req.user, documentId, dto); return await this.documentService.shareDocument(req.user, documentId, dto);

View File

@ -1,6 +1,5 @@
import { OperateUserAuthDto } from '@dtos/auth.dto'; import { OperateUserAuthDto } from '@dtos/auth.dto';
import { CreateOrganizationDto } from '@dtos/organization.dto'; import { CreateOrganizationDto } from '@dtos/organization.dto';
// import { OrganizationUserDto } from '@dtos/organization-user.dto';
import { JwtGuard } from '@guard/jwt.guard'; import { JwtGuard } from '@guard/jwt.guard';
import { import {
Body, Body,
@ -13,12 +12,13 @@ import {
Param, Param,
Patch, Patch,
Post, Post,
Query,
Request, Request,
UseGuards, UseGuards,
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { OrganizationService } from '@services/organization.service'; import { OrganizationService } from '@services/organization.service';
import { OrganizationApiDefinition } from '@think/domains'; import { IPagination, OrganizationApiDefinition } from '@think/domains';
@Controller('organization') @Controller('organization')
export class OrganizationController { export class OrganizationController {
@ -91,6 +91,20 @@ export class OrganizationController {
return await this.organizationService.getOrganizationDetail(req.user, id); return await this.organizationService.getOrganizationDetail(req.user, id);
} }
/**
*
* @param req
* @param id
* @returns
*/
@UseInterceptors(ClassSerializerInterceptor)
@Delete(OrganizationApiDefinition.deleteOrganization.server)
@HttpCode(HttpStatus.OK)
@UseGuards(JwtGuard)
async deleteWiki(@Request() req, @Param('id') id) {
return await this.organizationService.deleteOrganization(req.user, id);
}
/** /**
* *
* @param req * @param req
@ -100,8 +114,8 @@ export class OrganizationController {
@Get(OrganizationApiDefinition.getMembers.server) @Get(OrganizationApiDefinition.getMembers.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getMembers(@Request() req, @Param('id') id) { async getMembers(@Request() req, @Param('id') id, @Query() pagination: IPagination) {
return await this.organizationService.getMembers(req.user, id); return await this.organizationService.getMembers(req.user, id, pagination);
} }
/** /**

View File

@ -1,7 +1,15 @@
import { Controller } from '@nestjs/common'; import { ClassSerializerInterceptor, Controller, Get, HttpCode, HttpStatus, UseInterceptors } from '@nestjs/common';
import { SystemService } from '@services/system.service'; import { SystemService } from '@services/system.service';
import { SystemApiDefinition } from '@think/domains';
@Controller('system') @Controller('system')
export class SystemController { export class SystemController {
constructor(private readonly systemService: SystemService) {} constructor(private readonly systemService: SystemService) {}
@UseInterceptors(ClassSerializerInterceptor)
@Get(SystemApiDefinition.getPublicConfig.server)
@HttpCode(HttpStatus.CREATED)
async getPublicConfig() {
return await this.systemService.getPublicConfig();
}
} }

View File

@ -4,7 +4,6 @@ import { ShareWikiDto } from '@dtos/share-wiki.dto';
import { UpdateWikiDto } from '@dtos/update-wiki.dto'; import { UpdateWikiDto } from '@dtos/update-wiki.dto';
import { JwtGuard } from '@guard/jwt.guard'; import { JwtGuard } from '@guard/jwt.guard';
import { CheckWikiStatus, WikiStatusGuard } from '@guard/wiki-status.guard'; import { CheckWikiStatus, WikiStatusGuard } from '@guard/wiki-status.guard';
// import { CheckWikiUserRole, WikiUserRoleGuard } from '@guard/wiki-user.guard';
import { import {
Body, Body,
ClassSerializerInterceptor, ClassSerializerInterceptor,
@ -22,7 +21,7 @@ import {
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { WikiService } from '@services/wiki.service'; import { WikiService } from '@services/wiki.service';
import { IPagination, WikiApiDefinition, WikiStatus, WikiUserRole } from '@think/domains'; import { IPagination, WikiApiDefinition, WikiStatus } from '@think/domains';
@Controller('wiki') @Controller('wiki')
export class WikiController { export class WikiController {
@ -93,8 +92,6 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Get(WikiApiDefinition.getHomeDocumentById.server) @Get(WikiApiDefinition.getHomeDocumentById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole()
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getWikiHomeDocument(@Request() req, @Param('id') wikiId) { async getWikiHomeDocument(@Request() req, @Param('id') wikiId) {
return await this.wikiService.getWikiHomeDocument(req.user, wikiId); return await this.wikiService.getWikiHomeDocument(req.user, wikiId);
@ -109,8 +106,6 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Get(WikiApiDefinition.getTocsById.server) @Get(WikiApiDefinition.getTocsById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole()
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getWikiTocs(@Request() req, @Param('id') wikiId) { async getWikiTocs(@Request() req, @Param('id') wikiId) {
return await this.wikiService.getWikiTocs(req.user, wikiId); return await this.wikiService.getWikiTocs(req.user, wikiId);
@ -126,29 +121,11 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Patch(WikiApiDefinition.updateTocsById.server) @Patch(WikiApiDefinition.updateTocsById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole()
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async orderWikiTocs(@Body() relations) { async orderWikiTocs(@Body() relations) {
return await this.wikiService.orderWikiTocs(relations); return await this.wikiService.orderWikiTocs(relations);
} }
// /**
// * 获取知识库所有文档
// * @param req
// * @param wikiId
// * @returns
// */
// @UseInterceptors(ClassSerializerInterceptor)
// @Get(WikiApiDefinition.getDocumentsById.server)
// @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole()
// @UseGuards(WikiUserRoleGuard)
// @UseGuards(JwtGuard)
// async getWikiDocs(@Request() req, @Param('id') wikiId) {
// return await this.wikiService.getWikiDocs(req.user, wikiId);
// }
/** /**
* *
* @param req * @param req
@ -158,8 +135,6 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Get(WikiApiDefinition.getDetailById.server) @Get(WikiApiDefinition.getDetailById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole()
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getWikiDetail(@Request() req, @Param('id') wikiId) { async getWikiDetail(@Request() req, @Param('id') wikiId) {
return await this.wikiService.getWikiDetail(req.user, wikiId); return await this.wikiService.getWikiDetail(req.user, wikiId);
@ -167,7 +142,6 @@ export class WikiController {
/** /**
* *
*
* @param req * @param req
* @param wikiId * @param wikiId
* @param dto * @param dto
@ -176,8 +150,6 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Patch(WikiApiDefinition.updateById.server) @Patch(WikiApiDefinition.updateById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole(WikiUserRole.admin)
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async updateWiki(@Request() req, @Param('id') wikiId, @Body() dto: UpdateWikiDto) { async updateWiki(@Request() req, @Param('id') wikiId, @Body() dto: UpdateWikiDto) {
return await this.wikiService.updateWiki(req.user, wikiId, dto); return await this.wikiService.updateWiki(req.user, wikiId, dto);
@ -185,7 +157,6 @@ export class WikiController {
/** /**
* *
*
* @param req * @param req
* @param wikiId * @param wikiId
* @returns * @returns
@ -193,8 +164,6 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Delete(WikiApiDefinition.deleteById.server) @Delete(WikiApiDefinition.deleteById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole(WikiUserRole.admin)
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async deleteWiki(@Request() req, @Param('id') wikiId) { async deleteWiki(@Request() req, @Param('id') wikiId) {
return await this.wikiService.deleteWiki(req.user, wikiId); return await this.wikiService.deleteWiki(req.user, wikiId);
@ -202,7 +171,6 @@ export class WikiController {
/** /**
* *
*
* @param req * @param req
* @param wikiId * @param wikiId
* @returns * @returns
@ -210,16 +178,13 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Get(WikiApiDefinition.getMemberById.server) @Get(WikiApiDefinition.getMemberById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole(WikiUserRole.admin)
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async getWikiUsers(@Request() req, @Param('id') wikiId) { async getWikiUsers(@Request() req, @Param('id') wikiId, @Query() pagination: IPagination) {
return await this.wikiService.getWikiUsers(req.user, wikiId); return await this.wikiService.getWikiUsers(req.user, wikiId, pagination);
} }
/** /**
* *
*
* @param req * @param req
* @param wikiId * @param wikiId
* @param dto * @param dto
@ -228,8 +193,6 @@ export class WikiController {
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@Post(WikiApiDefinition.addMemberById.server) @Post(WikiApiDefinition.addMemberById.server)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
// @CheckWikiUserRole(WikiUserRole.admin)
// @UseGuards(WikiUserRoleGuard)
@UseGuards(JwtGuard) @UseGuards(JwtGuard)
async addWikiUser(@Request() req, @Param('id') wikiId, @Body() dto: OperateUserAuthDto) { async addWikiUser(@Request() req, @Param('id') wikiId, @Body() dto: OperateUserAuthDto) {
return await this.wikiService.addWikiUser(req.user, wikiId, dto); return await this.wikiService.addWikiUser(req.user, wikiId, dto);
@ -237,7 +200,6 @@ export class WikiController {
/** /**
* *
*
* @param req * @param req
* @param wikiId * @param wikiId
* @param dto * @param dto
@ -253,7 +215,6 @@ export class WikiController {
/** /**
* *
*
* @param req * @param req
* @param wikiId * @param wikiId
* @param dto * @param dto
@ -269,7 +230,6 @@ export class WikiController {
/** /**
* *
*
* @param req * @param req
* @param wikiId * @param wikiId
* @param dto * @param dto

View File

@ -1,4 +1,4 @@
import { IsEmail, IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; import { IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
/** /**
* *
@ -22,8 +22,8 @@ export class RegisterUserDto {
@MinLength(5, { message: '邮箱验证码至少5个字符' }) @MinLength(5, { message: '邮箱验证码至少5个字符' })
@IsString({ message: '邮箱验证码错误正确类型为String' }) @IsString({ message: '邮箱验证码错误正确类型为String' })
@IsNotEmpty({ message: '邮箱验证码不能为空' }) @IsOptional({ message: '邮箱验证码不能为空' })
verifyCode: string; verifyCode?: string;
} }
/** /**

View File

@ -1,15 +0,0 @@
import { IsBoolean, IsString } from 'class-validator';
export class DocAuthDto {
@IsString()
readonly documentId: string;
@IsString()
readonly userName: string;
@IsBoolean()
readonly readable: boolean;
@IsBoolean()
readonly editable: boolean;
}

View File

@ -1,4 +1,4 @@
import { IsEmail, IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
export class LoginUserDto { export class LoginUserDto {
@IsString({ message: '用户名称类型错误正确类型为String' }) @IsString({ message: '用户名称类型错误正确类型为String' })

View File

@ -1,12 +0,0 @@
// import { OrganizationAuthEnum } from '@think/domains';
// import { IsNotEmpty, IsString } from 'class-validator';
// export class OrganizationAuthDto {
// @IsString({ message: '权限类型类型错误正确类型为String' })
// @IsNotEmpty({ message: '权限类型不能为空' })
// auth: OrganizationAuthEnum;
// @IsString({ message: '组织 Id 类型错误正确类型为String' })
// @IsNotEmpty({ message: '组织 Id 不能为空' })
// organizationId: string;
// }

View File

@ -1,10 +0,0 @@
// import { OrganizationAuthEnum } from '@think/domains';
// import { IsString } from 'class-validator';
// export class OrganizationUserDto {
// @IsString()
// readonly userName: string;
// @IsString()
// readonly userAuth: OrganizationAuthEnum;
// }

View File

@ -1,10 +0,0 @@
import { WikiUserRole } from '@think/domains';
import { IsString } from 'class-validator';
export class WikiUserDto {
@IsString()
readonly userName: string;
@IsString()
readonly userRole: WikiUserRole;
}

View File

@ -8,10 +8,15 @@ export class SystemEntity {
/** /**
* *
*/ */
@Column({ type: 'boolean', default: false, comment: '是否锁定系统' }) @Column({ type: 'boolean', default: false, comment: '是否锁定系统' })
isSystemLocked: boolean; isSystemLocked: boolean;
/**
*
*/
@Column({ type: 'boolean', default: false, comment: '是否启用邮箱校验' })
enableEmailVerify: boolean;
/** /**
* *
*/ */

View File

@ -1,4 +1,4 @@
import { UserRole, UserStatus } from '@think/domains'; import { UserStatus } from '@think/domains';
import * as bcrypt from 'bcryptjs'; import * as bcrypt from 'bcryptjs';
import { Exclude } from 'class-transformer'; import { Exclude } from 'class-transformer';
import { BeforeInsert, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { BeforeInsert, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
@ -37,14 +37,6 @@ export class UserEntity {
@Column({ type: 'boolean', default: false, comment: '是否为系统管理员' }) @Column({ type: 'boolean', default: false, comment: '是否为系统管理员' })
isSystemAdmin: boolean; isSystemAdmin: boolean;
@Column({
type: 'enum',
enum: UserRole,
default: UserRole.normal,
comment: '用户角色',
})
public role: UserRole;
@Column({ @Column({
type: 'enum', type: 'enum',
enum: UserStatus, enum: UserStatus,

View File

@ -1,27 +0,0 @@
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
@Entity('verify')
export class VerifyEntity {
@PrimaryGeneratedColumn('uuid')
public id: string;
@Column({ type: 'varchar', comment: '邮箱地址' })
public email: string;
@Column({ type: 'varchar', comment: '验证码' })
public verifyCode: string;
@CreateDateColumn({
type: 'timestamp',
name: 'createdAt',
comment: '创建时间',
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp',
name: 'updatedAt',
comment: '更新时间',
})
updatedAt: Date;
}

View File

@ -1,34 +0,0 @@
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
@Entity('view')
export class ViewEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', comment: '文档 Id' })
documentId: string;
// public 表示从公开渠道访问
@Column({ type: 'varchar', comment: '访问用户 Id', default: 'public' })
userId: string;
@Column({ type: 'mediumtext', default: null, charset: 'utf8mb4' })
originUserAgent: string;
@Column({ type: 'mediumtext', default: null, charset: 'utf8mb4' })
parsedUserAgent: string;
@CreateDateColumn({
type: 'timestamp',
name: 'createdAt',
comment: '创建时间',
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp',
name: 'updatedAt',
comment: '更新时间',
})
updatedAt: Date;
}

View File

@ -1,5 +1,6 @@
import { CommentController } from '@controllers/comment.controller'; import { CommentController } from '@controllers/comment.controller';
import { CommentEntity } from '@entities/comment.entity'; import { CommentEntity } from '@entities/comment.entity';
import { AuthModule } from '@modules/auth.module';
import { DocumentModule } from '@modules/document.module'; import { DocumentModule } from '@modules/document.module';
import { MessageModule } from '@modules/message.module'; import { MessageModule } from '@modules/message.module';
import { UserModule } from '@modules/user.module'; import { UserModule } from '@modules/user.module';
@ -11,6 +12,7 @@ import { CommentService } from '@services/comment.service';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([CommentEntity]), TypeOrmModule.forFeature([CommentEntity]),
forwardRef(() => AuthModule),
forwardRef(() => UserModule), forwardRef(() => UserModule),
forwardRef(() => WikiModule), forwardRef(() => WikiModule),
forwardRef(() => DocumentModule), forwardRef(() => DocumentModule),

View File

@ -3,6 +3,7 @@ import { OrganizationEntity } from '@entities/organization.entity';
import { AuthModule } from '@modules/auth.module'; import { AuthModule } from '@modules/auth.module';
import { MessageModule } from '@modules/message.module'; import { MessageModule } from '@modules/message.module';
import { UserModule } from '@modules/user.module'; import { UserModule } from '@modules/user.module';
import { WikiModule } from '@modules/wiki.module';
import { forwardRef, Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { OrganizationService } from '@services/organization.service'; import { OrganizationService } from '@services/organization.service';
@ -13,6 +14,7 @@ import { OrganizationService } from '@services/organization.service';
forwardRef(() => UserModule), forwardRef(() => UserModule),
forwardRef(() => MessageModule), forwardRef(() => MessageModule),
forwardRef(() => AuthModule), forwardRef(() => AuthModule),
forwardRef(() => WikiModule),
], ],
providers: [OrganizationService], providers: [OrganizationService],
exports: [OrganizationService], exports: [OrganizationService],

View File

@ -1,12 +1,10 @@
import { VerifyController } from '@controllers/verify.controller'; import { VerifyController } from '@controllers/verify.controller';
import { VerifyEntity } from '@entities/verify.entity';
import { SystemModule } from '@modules/system.module'; import { SystemModule } from '@modules/system.module';
import { forwardRef, Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { VerifyService } from '@services/verify.service'; import { VerifyService } from '@services/verify.service';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([VerifyEntity]), forwardRef(() => SystemModule)], imports: [forwardRef(() => SystemModule)],
providers: [VerifyService], providers: [VerifyService],
exports: [VerifyService], exports: [VerifyService],
controllers: [VerifyController], controllers: [VerifyController],

View File

@ -1,11 +1,8 @@
import { ViewController } from '@controllers/view.controller'; import { ViewController } from '@controllers/view.controller';
import { ViewEntity } from '@entities/view.entity';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ViewService } from '@services/view.service'; import { ViewService } from '@services/view.service';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([ViewEntity])],
providers: [ViewService], providers: [ViewService],
exports: [ViewService], exports: [ViewService],
controllers: [ViewController], controllers: [ViewController],

View File

@ -3,7 +3,7 @@ import { AuthEntity } from '@entities/auth.entity';
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { AuthEnum, IDocument, IOrganization, IUser, IWiki } from '@think/domains'; import { AuthEnum, IDocument, IOrganization, IPagination, IUser, IWiki } from '@think/domains';
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -41,8 +41,6 @@ export class AuthService {
const wrappedAuth = { userId, ...auth }; const wrappedAuth = { userId, ...auth };
const oldAuth = await this.authRepo.findOne(wrappedAuth); const oldAuth = await this.authRepo.findOne(wrappedAuth);
// TODO: 这里可以判断权限继承
let newAuth: AuthEntity; let newAuth: AuthEntity;
if (oldAuth) { if (oldAuth) {
@ -80,6 +78,48 @@ export class AuthService {
} }
} }
/**
*
* canDelete
* @param organizationId
*/
async deleteOrganization(organizationId: IOrganization['id']) {
const res = await this.authRepo.find({
organizationId,
});
await this.authRepo.remove(res);
}
/**
*
* canDelete
* @param organizationId
* @param wikiId
*/
async deleteWiki(organizationId: IOrganization['id'], wikiId: IWiki['id']) {
const res = await this.authRepo.find({
organizationId,
wikiId,
});
await this.authRepo.remove(res);
}
/**
*
* canDelete
* @param organizationId
* @param wikiId
* @param documentId
*/
async deleteDocument(organizationId: IOrganization['id'], wikiId: IWiki['id'], documentId: IDocument['id']) {
const res = await this.authRepo.find({
organizationId,
wikiId,
documentId,
});
await this.authRepo.remove(res);
}
/** /**
* *
* @param userId * @param userId
@ -97,7 +137,7 @@ export class AuthService {
const userAuth = await this.authRepo.findOne(conditions); const userAuth = await this.authRepo.findOne(conditions);
if (!userAuth || userAuth.auth === AuthEnum.noAccess) { if (!userAuth || userAuth.auth === AuthEnum.noAccess) {
throw new HttpException('您没有权限查看', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限', HttpStatus.FORBIDDEN);
} }
return userAuth; return userAuth;
@ -120,7 +160,7 @@ export class AuthService {
const userAuth = await this.authRepo.findOne(conditions); const userAuth = await this.authRepo.findOne(conditions);
if (!userAuth || ![AuthEnum.creator, AuthEnum.admin].includes(userAuth.auth)) { if (!userAuth || ![AuthEnum.creator, AuthEnum.admin].includes(userAuth.auth)) {
throw new HttpException('您没有权限编辑', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限', HttpStatus.FORBIDDEN);
} }
return userAuth; return userAuth;
@ -143,7 +183,7 @@ export class AuthService {
const userAuth = await this.authRepo.findOne(conditions); const userAuth = await this.authRepo.findOne(conditions);
if (!userAuth || ![AuthEnum.creator].includes(userAuth.auth)) { if (!userAuth || ![AuthEnum.creator].includes(userAuth.auth)) {
throw new HttpException('您没有权限删除', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限', HttpStatus.FORBIDDEN);
} }
return userAuth; return userAuth;
@ -156,6 +196,12 @@ export class AuthService {
* @param dto * @param dto
*/ */
private async operateOtherUserAuth(currentUserId: IUser['id'], targetUserId: IUser['id'], dto: AuthDto) { private async operateOtherUserAuth(currentUserId: IUser['id'], targetUserId: IUser['id'], dto: AuthDto) {
const targetUser = await this.userService.findOne({ id: targetUserId });
if (!targetUser) {
throw new HttpException('用户不存在', HttpStatus.NOT_FOUND);
}
const conditions: Partial<AuthEntity> = { const conditions: Partial<AuthEntity> = {
organizationId: dto.organizationId, organizationId: dto.organizationId,
wikiId: dto.wikiId || null, wikiId: dto.wikiId || null,
@ -168,17 +214,17 @@ export class AuthService {
}); });
if (!currentUserAuth) { if (!currentUserAuth) {
throw new HttpException('您没有权限操作1', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限操作', HttpStatus.FORBIDDEN);
} }
// 仅创建者、管理员可操作他人权限 // 仅创建者、管理员可操作他人权限
if (![AuthEnum.creator, AuthEnum.admin].includes(currentUserAuth.auth)) { if (![AuthEnum.creator, AuthEnum.admin].includes(currentUserAuth.auth)) {
throw new HttpException('您没有权限操作2', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限操作', HttpStatus.FORBIDDEN);
} }
// 仅创建者可赋予他人创建者、管理员权限 // 仅创建者可赋予他人创建者、管理员权限
if ([AuthEnum.creator, AuthEnum.admin].includes(dto.auth) && currentUserAuth.auth !== AuthEnum.creator) { if ([AuthEnum.creator, AuthEnum.admin].includes(dto.auth) && currentUserAuth.auth !== AuthEnum.creator) {
throw new HttpException('您没有权限操作3', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限操作', HttpStatus.FORBIDDEN);
} }
const maybeTargetUserAuth = await this.authRepo.findOne({ const maybeTargetUserAuth = await this.authRepo.findOne({
@ -189,12 +235,12 @@ export class AuthService {
if (maybeTargetUserAuth) { if (maybeTargetUserAuth) {
// 对方是创建者,无权操作 // 对方是创建者,无权操作
if (maybeTargetUserAuth.auth === AuthEnum.creator) { if (maybeTargetUserAuth.auth === AuthEnum.creator) {
throw new HttpException('您没有权限操作4', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限操作', HttpStatus.FORBIDDEN);
} }
// 对方是管理员,仅创建者可操作 // 对方是管理员,仅创建者可操作
if (maybeTargetUserAuth.auth === AuthEnum.admin && currentUserAuth.auth !== AuthEnum.creator) { if (maybeTargetUserAuth.auth === AuthEnum.admin && currentUserAuth.auth !== AuthEnum.creator) {
throw new HttpException('您没有权限操作5', HttpStatus.FORBIDDEN); throw new HttpException('您没有权限操作', HttpStatus.FORBIDDEN);
} }
} }
} }
@ -300,10 +346,12 @@ export class AuthService {
/** /**
* *
* @param userId * @param organizationId
* @param pagination
* @returns
*/ */
async getUsersAuthInOrganization(organizationId: IOrganization['id']) { async getUsersAuthInOrganization(organizationId: IOrganization['id'], pagination: IPagination | null) {
const [data, total] = await this.authRepo const query = await this.authRepo
.createQueryBuilder('auth') .createQueryBuilder('auth')
.where('auth.auth IN (:...types)', { .where('auth.auth IN (:...types)', {
types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess], types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess],
@ -311,18 +359,28 @@ export class AuthService {
.andWhere('auth.organizationId=:organizationId') .andWhere('auth.organizationId=:organizationId')
.andWhere('auth.wikiId is NULL') .andWhere('auth.wikiId is NULL')
.andWhere('auth.documentId is NULL') .andWhere('auth.documentId is NULL')
.setParameter('organizationId', organizationId) .setParameter('organizationId', organizationId);
.getManyAndCount();
if (pagination) {
const { page = 1, pageSize = 12 } = pagination;
query.skip((+page - 1) * +pageSize);
query.take(+pageSize);
}
const [data, total] = await query.getManyAndCount();
return { data: data || [], total }; return { data: data || [], total };
} }
/** /**
* *
* @param userId * @param organizationId
* @param wikiId
* @param pagination
* @returns
*/ */
async getUsersAuthInWiki(organizationId: IOrganization['id'], wikiId: IWiki['id']) { async getUsersAuthInWiki(organizationId: IOrganization['id'], wikiId: IWiki['id'], pagination: IPagination | null) {
const [data, total] = await this.authRepo const query = await this.authRepo
.createQueryBuilder('auth') .createQueryBuilder('auth')
.where('auth.auth IN (:...types)', { .where('auth.auth IN (:...types)', {
types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess], types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess],
@ -331,8 +389,14 @@ export class AuthService {
.andWhere('auth.wikiId=:wikiId') .andWhere('auth.wikiId=:wikiId')
.andWhere('auth.documentId is NULL') .andWhere('auth.documentId is NULL')
.setParameter('organizationId', organizationId) .setParameter('organizationId', organizationId)
.setParameter('wikiId', wikiId) .setParameter('wikiId', wikiId);
.getManyAndCount();
if (pagination) {
const { page = 1, pageSize = 12 } = pagination;
query.skip((+page - 1) * +pageSize);
query.take(+pageSize);
}
const [data, total] = await query.getManyAndCount();
return { data: data || [], total }; return { data: data || [], total };
} }
@ -413,11 +477,20 @@ export class AuthService {
} }
/** /**
* *
* @param userId * @param organizationId
* @param wikiId
* @param documentId
* @param pagination
* @returns
*/ */
async getUsersAuthInDocument(organizationId: IOrganization['id'], wikiId: IWiki['id'], documentId: IDocument['id']) { async getUsersAuthInDocument(
const [data, total] = await this.authRepo organizationId: IOrganization['id'],
wikiId: IWiki['id'],
documentId: IDocument['id'],
pagination: IPagination | null
) {
const query = await this.authRepo
.createQueryBuilder('auth') .createQueryBuilder('auth')
.where('auth.auth IN (:...types)', { .where('auth.auth IN (:...types)', {
types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess], types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess],
@ -427,8 +500,14 @@ export class AuthService {
.andWhere('auth.documentId=:documentId') .andWhere('auth.documentId=:documentId')
.setParameter('organizationId', organizationId) .setParameter('organizationId', organizationId)
.setParameter('wikiId', wikiId) .setParameter('wikiId', wikiId)
.setParameter('documentId', documentId) .setParameter('documentId', documentId);
.getManyAndCount();
if (pagination) {
const { page = 1, pageSize = 12 } = pagination;
query.skip((+page - 1) * +pageSize);
query.take(+pageSize);
}
const [data, total] = await query.getManyAndCount();
return { data: data || [], total }; return { data: data || [], total };
} }

View File

@ -5,8 +5,8 @@ import { ConfigService } from '@nestjs/config';
import { DocumentService } from '@services/document.service'; import { DocumentService } from '@services/document.service';
import { DocumentVersionService } from '@services/document-version.service'; import { DocumentVersionService } from '@services/document-version.service';
import { TemplateService } from '@services/template.service'; import { TemplateService } from '@services/template.service';
import { OutUser, UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { DocumentStatus } from '@think/domains'; import { DocumentStatus, IUser } from '@think/domains';
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import * as Y from 'yjs'; import * as Y from 'yjs';
@ -101,14 +101,16 @@ export class CollaborationService {
} }
return { user: { name: '匿名用户' } }; return { user: { name: '匿名用户' } };
} else { } else {
// TODO权限校验 const authority = await this.documentService.getDocumentUserAuth(user.id, targetId);
// const authority = await this.documentService.getDocumentAuthority(targetId, user.id);
// if (!authority.readable) { if (!authority.readable) {
// throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN); throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
// } }
// if (!authority.editable) {
// connection.readOnly = true; if (!authority.editable) {
// } connection.readOnly = true;
}
return { return {
user, user,
}; };
@ -121,9 +123,11 @@ export class CollaborationService {
} }
const template = await this.templateService.findById(targetId); const template = await this.templateService.findById(targetId);
if (template.createUserId !== user.id) { if (template.createUserId !== user.id) {
throw new HttpException('您无权查看此模板', HttpStatus.FORBIDDEN); throw new HttpException('您无权查看此模板', HttpStatus.FORBIDDEN);
} }
return { return {
user, user,
}; };
@ -149,6 +153,9 @@ export class CollaborationService {
switch (docType) { switch (docType) {
case 'document': { case 'document': {
const res = await this.documentService.findById(targetId); const res = await this.documentService.findById(targetId);
if (!res) {
throw new Error('文档不存在');
}
state = res.state; state = res.state;
break; break;
} }
@ -179,7 +186,7 @@ export class CollaborationService {
const docType = requestParameters.get('docType'); const docType = requestParameters.get('docType');
const userId = requestParameters.get('userId'); const userId = requestParameters.get('userId');
const updateDocument = async (user: OutUser, documentId: string, data) => { const updateDocument = async (user: IUser, documentId: string, data) => {
await this.documentService.updateDocument(user, documentId, data); await this.documentService.updateDocument(user, documentId, data);
this.debounce( this.debounce(
`onStoreDocumentVersion-${documentId}`, `onStoreDocumentVersion-${documentId}`,
@ -218,7 +225,7 @@ export class CollaborationService {
const node = TiptapTransformer.fromYdoc(data.document); const node = TiptapTransformer.fromYdoc(data.document);
const title = lodash.get(node, `default.content[0].content[0].text`, '').replace(/\s/g, '').slice(0, 255); const title = lodash.get(node, `default.content[0].content[0].text`, '').replace(/\s/g, '').slice(0, 255);
const state = Buffer.from(Y.encodeStateAsUpdate(data.document)); const state = Buffer.from(Y.encodeStateAsUpdate(data.document));
await updateHandler({ id: userId } as OutUser, targetId, { await updateHandler({ id: userId } as IUser, targetId, {
title, title,
content: JSON.stringify(node), content: JSON.stringify(node),
state, state,
@ -234,7 +241,7 @@ export class CollaborationService {
if (docType === 'document') { if (docType === 'document') {
const data = await this.documentService.findById(targetId); const data = await this.documentService.findById(targetId);
if (data && !data.title) { if (data && !data.title) {
await this.documentService.updateDocument({ id: userId } as OutUser, targetId, { await this.documentService.updateDocument({ id: userId } as IUser, targetId, {
title: '未命名文档', title: '未命名文档',
}); });
} }
@ -244,7 +251,7 @@ export class CollaborationService {
if (docType === 'template') { if (docType === 'template') {
const data = await this.templateService.findById(targetId); const data = await this.templateService.findById(targetId);
if (data && !data.title) { if (data && !data.title) {
await this.templateService.updateTemplate({ id: userId } as OutUser, targetId, { await this.templateService.updateTemplate({ id: userId } as IUser, targetId, {
title: '未命名模板', title: '未命名模板',
}); });
} }

View File

@ -3,10 +3,11 @@ import { CommentEntity } from '@entities/comment.entity';
import { parseUserAgent } from '@helpers/ua.helper'; import { parseUserAgent } from '@helpers/ua.helper';
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { AuthService } from '@services/auth.service';
import { DocumentService } from '@services/document.service'; import { DocumentService } from '@services/document.service';
import { MessageService } from '@services/message.service'; import { MessageService } from '@services/message.service';
import { OutUser, UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { buildMessageURL, DocumentStatus } from '@think/domains'; import { AuthEnum, buildMessageURL, DocumentStatus, IUser } from '@think/domains';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@Injectable() @Injectable()
@ -14,9 +15,17 @@ export class CommentService {
constructor( constructor(
@InjectRepository(CommentEntity) @InjectRepository(CommentEntity)
private readonly commentRepo: Repository<CommentEntity>, private readonly commentRepo: Repository<CommentEntity>,
@Inject(forwardRef(() => AuthService))
private readonly authService: AuthService,
@Inject(forwardRef(() => UserService))
private readonly userService: UserService,
@Inject(forwardRef(() => MessageService)) @Inject(forwardRef(() => MessageService))
private readonly messageService: MessageService, private readonly messageService: MessageService,
private readonly userService: UserService,
@Inject(forwardRef(() => DocumentService))
private readonly documentService: DocumentService private readonly documentService: DocumentService
) {} ) {}
@ -44,20 +53,20 @@ export class CommentService {
* @param dto * @param dto
* @returns * @returns
*/ */
async create(user: OutUser, userAgent: string, dto: CommentDto) { async create(user: IUser, userAgent: string, dto: CommentDto) {
const { documentId, html, replyUserId } = dto; const { documentId, html, replyUserId } = dto;
const doc = await this.documentService.findById(documentId); const doc = await this.documentService.findById(documentId);
if (doc.status !== DocumentStatus.public) { if (doc.status !== DocumentStatus.public) {
// TODO权限校验 const authority = await this.documentService.getDocumentUserAuth(user.id, documentId);
// const docAuth = await this.documentService.getDocumentAuthority(documentId, user.id);
// if (!docAuth) { if (!authority) {
// throw new HttpException('文档不存在', HttpStatus.NOT_FOUND); throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
// } }
// if (!docAuth.readable) { if (!authority.readable) {
// throw new HttpException('权限不足,无法评论', HttpStatus.FORBIDDEN); throw new HttpException('权限不足,无法评论', HttpStatus.FORBIDDEN);
// } }
} }
const { text: uaText } = parseUserAgent(userAgent); const { text: uaText } = parseUserAgent(userAgent);
@ -74,21 +83,23 @@ export class CommentService {
const res = await this.commentRepo.create(comment); const res = await this.commentRepo.create(comment);
const ret = await this.commentRepo.save(res); const ret = await this.commentRepo.save(res);
// const wikiUsersAuth = await this.documentService.getDocUsersWithoutAuthCheck(user, documentId); const { data: users } = await this.authService.getUsersAuthInDocument(doc.organizationId, doc.wikiId, doc.id, null);
// await Promise.all( await Promise.all(
// wikiUsersAuth.map(async (userAuth) => { users
// await this.messageService.notify(userAuth.user, { .filter((user) => user.auth !== AuthEnum.noAccess)
// title: `文档「${doc.title}」收到新评论`, .map((user) => {
// message: `文档「${doc.title}」收到新评论,快去看看!`, this.messageService.notify(user.userId, {
// url: buildMessageURL('toDocument')({ title: `文档「${doc.title}」收到新评论`,
// organizationId: doc.organizationId, message: `文档「${doc.title}」收到新评论,快去看看!`,
// wikiId: doc.wikiId, url: buildMessageURL('toDocument')({
// documentId: doc.id, organizationId: doc.organizationId,
// }), wikiId: doc.wikiId,
// }); documentId: doc.id,
// }) }),
// ); });
})
);
return ret; return ret;
} }
@ -180,21 +191,24 @@ export class CommentService {
const newData = await this.commentRepo.merge(old, { html: dto.html }); const newData = await this.commentRepo.merge(old, { html: dto.html });
const doc = await this.documentService.findById(old.documentId); const doc = await this.documentService.findById(old.documentId);
// const wikiUsersAuth = await this.documentService.getDocUsersWithoutAuthCheck(user, old.documentId);
// await Promise.all( const { data: users } = await this.authService.getUsersAuthInDocument(doc.organizationId, doc.wikiId, doc.id, null);
// wikiUsersAuth.map(async (userAuth) => {
// await this.messageService.notify(userAuth.user, { await Promise.all(
// title: `文档「${doc.title}」评论更新`, users
// message: `文档「${doc.title}」的评论已更新,快去看看!`, .filter((user) => user.auth !== AuthEnum.noAccess)
// url: buildMessageURL('toDocument')({ .map((user) => {
// organizationId: doc.organizationId, this.messageService.notify(user.userId, {
// wikiId: doc.wikiId, title: `文档「${doc.title}」收到新评论`,
// documentId: doc.id, message: `文档「${doc.title}」收到新评论,快去看看!`,
// }), url: buildMessageURL('toDocument')({
// }); organizationId: doc.organizationId,
// }) wikiId: doc.wikiId,
// ); documentId: doc.id,
}),
});
})
);
return this.commentRepo.save(newData); return this.commentRepo.save(newData);
} }
@ -205,20 +219,25 @@ export class CommentService {
throw new HttpException('您不是评论创建者,无法删除', HttpStatus.FORBIDDEN); throw new HttpException('您不是评论创建者,无法删除', HttpStatus.FORBIDDEN);
} }
const doc = await this.documentService.findById(data.documentId); const doc = await this.documentService.findById(data.documentId);
// const wikiUsersAuth = await this.documentService.getDocUsersWithoutAuthCheck(user, data.documentId);
// await Promise.all( const { data: users } = await this.authService.getUsersAuthInDocument(doc.organizationId, doc.wikiId, doc.id, null);
// wikiUsersAuth.map(async (userAuth) => {
// await this.messageService.notify(userAuth.user, { await Promise.all(
// title: `文档「${doc.title}」的评论已被删除`, users
// message: `文档「${doc.title}」的评论已被删除,快去看看`, .filter((user) => user.auth !== AuthEnum.noAccess)
// url: buildMessageURL('toDocument')({ .map((user) => {
// organizationId: doc.organizationId, this.messageService.notify(user.userId, {
// wikiId: doc.wikiId, title: `文档「${doc.title}」收到新评论`,
// documentId: doc.id, message: `文档「${doc.title}」收到新评论,快去看看!`,
// }), url: buildMessageURL('toDocument')({
// }); organizationId: doc.organizationId,
// }) wikiId: doc.wikiId,
// ); documentId: doc.id,
}),
});
})
);
return this.commentRepo.remove(data); return this.commentRepo.remove(data);
} }
} }

View File

@ -1,6 +1,5 @@
import { OperateUserAuthDto } from '@dtos/auth.dto'; import { OperateUserAuthDto } from '@dtos/auth.dto';
import { CreateDocumentDto } from '@dtos/create-document.dto'; import { CreateDocumentDto } from '@dtos/create-document.dto';
// import { DocAuthDto } from '@dtos/doc-auth.dto';
import { ShareDocumentDto } from '@dtos/share-document.dto'; import { ShareDocumentDto } from '@dtos/share-document.dto';
import { UpdateDocumentDto } from '@dtos/update-document.dto'; import { UpdateDocumentDto } from '@dtos/update-document.dto';
import { DocumentEntity } from '@entities/document.entity'; import { DocumentEntity } from '@entities/document.entity';
@ -12,11 +11,11 @@ import { CollaborationService } from '@services/collaboration.service';
import { DocumentVersionService } from '@services/document-version.service'; import { DocumentVersionService } from '@services/document-version.service';
import { MessageService } from '@services/message.service'; import { MessageService } from '@services/message.service';
import { TemplateService } from '@services/template.service'; import { TemplateService } from '@services/template.service';
import { OutUser, UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { ViewService } from '@services/view.service'; import { ViewService } from '@services/view.service';
import { WikiService } from '@services/wiki.service'; import { WikiService } from '@services/wiki.service';
import { EMPTY_DOCUMNENT } from '@think/constants'; import { EMPTY_DOCUMNENT } from '@think/constants';
import { AuthEnum, buildMessageURL, DocumentStatus, WikiUserRole } from '@think/domains'; import { AuthEnum, buildMessageURL, DocumentStatus, IUser } from '@think/domains';
import { instanceToPlain } from 'class-transformer'; import { instanceToPlain } from 'class-transformer';
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -27,9 +26,6 @@ export class DocumentService {
private documentVersionService: DocumentVersionService; private documentVersionService: DocumentVersionService;
constructor( constructor(
// @InjectRepository(DocumentUserEntity)
// public readonly documentUserRepo: Repository<DocumentUserEntity>,
@InjectRepository(DocumentEntity) @InjectRepository(DocumentEntity)
public readonly documentRepo: Repository<DocumentEntity>, public readonly documentRepo: Repository<DocumentEntity>,
@ -70,7 +66,7 @@ export class DocumentService {
* @param dto * @param dto
* @returns * @returns
*/ */
public async findById(id: string) { public async findById(id: string): Promise<Partial<DocumentEntity>> {
const document = await this.documentRepo.findOne(id); const document = await this.documentRepo.findOne(id);
return instanceToPlain(document); return instanceToPlain(document);
} }
@ -81,7 +77,7 @@ export class DocumentService {
* @param dto * @param dto
* @returns * @returns
*/ */
public async findByIds(ids: string[]) { public async findByIds(ids: string[]): Promise<Array<Partial<DocumentEntity>>> {
const documents = await this.documentRepo.findByIds(ids); const documents = await this.documentRepo.findByIds(ids);
return documents.map((doc) => instanceToPlain(doc)); return documents.map((doc) => instanceToPlain(doc));
} }
@ -95,90 +91,6 @@ export class DocumentService {
return await this.documentRepo.findOne({ wikiId, isWikiHome: true }); return await this.documentRepo.findOne({ wikiId, isWikiHome: true });
} }
// /**
// * 获取用户在指定文档的权限
// * @param documentId
// * @param userId
// * @returns
// */
// public async getDocumentAuthority(documentId: string, userId: string) {
// const authority = await this.documentUserRepo.findOne({
// documentId,
// userId,
// });
// return authority;
// }
/**
*
* @param param0
* @returns
// */
// async operateDocumentAuth({ currentUserId, documentId, targetUserId, readable = false, editable = false }) {
// const doc = await this.documentRepo.findOne({ id: documentId });
// if (!doc) {
// throw new HttpException('文档不存在', HttpStatus.BAD_REQUEST);
// }
// const isCurrentUserCreator = currentUserId === doc.createUserId;
// const isTargetUserCreator = targetUserId === doc.createUserId;
// if (!isCurrentUserCreator) {
// throw new HttpException('您不是文档创建者,无权操作', HttpStatus.FORBIDDEN);
// }
// const targetUser = await this.userService.findOne(targetUserId);
// const targetDocAuth = await this.documentUserRepo.findOne({
// documentId,
// userId: targetUserId,
// });
// if (!targetDocAuth) {
// const documentUser = {
// documentId,
// createUserId: doc.createUserId,
// wikiId: doc.wikiId,
// userId: targetUserId,
// readable: isTargetUserCreator ? true : editable ? true : readable,
// editable: isTargetUserCreator ? true : editable,
// };
// const res = await this.documentUserRepo.create(documentUser);
// const ret = await this.documentUserRepo.save(res);
// await this.messageService.notify(targetUser, {
// title: `您已被添加到文档「${doc.title}」`,
// message: `您已被添加到文档「${doc.title}」,快去看看!`,
// url: buildMessageURL('toDocument')({
// organizationId: doc.organizationId,
// wikiId: doc.wikiId,
// documentId: doc.id,
// }),
// });
// return ret;
// } else {
// const newData = {
// ...targetDocAuth,
// readable: isTargetUserCreator ? true : editable ? true : readable,
// editable: isTargetUserCreator ? true : editable,
// };
// const res = await this.documentUserRepo.merge(targetDocAuth, newData);
// const ret = await this.documentUserRepo.save(res);
// await this.messageService.notify(targetUser, {
// title: `您在文档「${doc.title}」的权限已变更`,
// message: `您在文档「${doc.title}」的权限已变更,快去看看!`,
// url: buildMessageURL('toDocument')({
// organizationId: doc.organizationId,
// wikiId: doc.wikiId,
// documentId: doc.id,
// }),
// });
// return ret;
// }
// }
/** /**
* *
* @param user * @param user
@ -186,7 +98,7 @@ export class DocumentService {
* @param dto * @param dto
* @returns * @returns
*/ */
async addDocUser(user: OutUser, documentId, dto: OperateUserAuthDto) { async addDocUser(user: IUser, documentId, dto: OperateUserAuthDto) {
const targetUser = await this.userService.findOne({ name: dto.userName }); const targetUser = await this.userService.findOne({ name: dto.userName });
if (!targetUser) { if (!targetUser) {
@ -199,12 +111,32 @@ export class DocumentService {
throw new HttpException('目标文档不存在', HttpStatus.NOT_FOUND); throw new HttpException('目标文档不存在', HttpStatus.NOT_FOUND);
} }
if (
!(await this.authService.getAuth(targetUser.id, {
organizationId: doc.organizationId,
wikiId: null,
documentId: null,
}))
) {
throw new HttpException('该用户非组织成员', HttpStatus.FORBIDDEN);
}
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, { await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
auth: dto.userAuth, auth: dto.userAuth,
organizationId: doc.organizationId, organizationId: doc.organizationId,
wikiId: doc.wikiId, wikiId: doc.wikiId,
documentId: doc.id, documentId: doc.id,
}); });
await this.messageService.notify(targetUser.id, {
title: `您被添加到文档「${doc.title}`,
message: `您被添加到文档「${doc.title}」,快去看看吧!`,
url: buildMessageURL('toWiki')({
organizationId: doc.organizationId,
wikiId: doc.wikiId,
documentId: doc.id,
}),
});
} }
/** /**
@ -214,7 +146,7 @@ export class DocumentService {
* @param dto * @param dto
* @returns * @returns
*/ */
async updateDocUser(user: OutUser, documentId, dto: OperateUserAuthDto) { async updateDocUser(user: IUser, documentId, dto: OperateUserAuthDto) {
const targetUser = await this.userService.findOne({ name: dto.userName }); const targetUser = await this.userService.findOne({ name: dto.userName });
if (!targetUser) { if (!targetUser) {
@ -233,6 +165,16 @@ export class DocumentService {
wikiId: doc.wikiId, wikiId: doc.wikiId,
documentId: doc.id, documentId: doc.id,
}); });
await this.messageService.notify(targetUser.id, {
title: `文档「${doc.title}」权限更新`,
message: `您在文档「${doc.title}」的权限已变更,快去看看吧!`,
url: buildMessageURL('toWiki')({
organizationId: doc.organizationId,
wikiId: doc.wikiId,
documentId: doc.id,
}),
});
} }
/** /**
@ -242,7 +184,7 @@ export class DocumentService {
* @param dto * @param dto
* @returns * @returns
*/ */
async deleteDocUser(user: OutUser, documentId, dto: OperateUserAuthDto) { async deleteDocUser(user: IUser, documentId, dto: OperateUserAuthDto) {
const targetUser = await this.userService.findOne({ name: dto.userName }); const targetUser = await this.userService.findOne({ name: dto.userName });
if (!targetUser) { if (!targetUser) {
@ -261,6 +203,16 @@ export class DocumentService {
wikiId: doc.wikiId, wikiId: doc.wikiId,
documentId: doc.id, documentId: doc.id,
}); });
await this.messageService.notify(targetUser.id, {
title: `文档「${doc.title}」权限已收回`,
message: `您在文档「${doc.title}」的权限已收回!`,
url: buildMessageURL('toWiki')({
organizationId: doc.organizationId,
wikiId: doc.wikiId,
documentId: doc.id,
}),
});
} }
/** /**
@ -268,7 +220,7 @@ export class DocumentService {
* @param userId * @param userId
* @param wikiId * @param wikiId
*/ */
async getDocUsers(user: OutUser, documentId) { async getDocUsers(user: IUser, documentId, pagination) {
const doc = await this.documentRepo.findOne({ id: documentId }); const doc = await this.documentRepo.findOne({ id: documentId });
if (!doc) { if (!doc) {
@ -284,7 +236,8 @@ export class DocumentService {
const { data: auths, total } = await this.authService.getUsersAuthInDocument( const { data: auths, total } = await this.authService.getUsersAuthInDocument(
doc.organizationId, doc.organizationId,
doc.wikiId, doc.wikiId,
doc.id doc.id,
pagination
); );
const res = await Promise.all( const res = await Promise.all(
@ -297,29 +250,6 @@ export class DocumentService {
return { data: res, total }; return { data: res, total };
} }
// /**
// * 获取文档成员
// * 忽略权限检查
// * @param userId
// * @param wikiId
// */
// async getDocUsersWithoutAuthCheck(user: OutUser, documentId) {
// const doc = await this.documentRepo.findOne({ id: documentId });
// if (!doc) {
// throw new HttpException('文档不存在', HttpStatus.BAD_REQUEST);
// }
// const data = await this.documentUserRepo.find({ documentId });
// return await Promise.all(
// data.map(async (auth) => {
// const user = await this.userService.findById(auth.userId);
// return { auth, user };
// })
// );
// }
/** /**
* *
* @param user * @param user
@ -327,7 +257,7 @@ export class DocumentService {
* @param isWikiHome * @param isWikiHome
* @returns * @returns
*/ */
public async createDocument(user: OutUser, dto: CreateDocumentDto, isWikiHome = false) { public async createDocument(user: IUser, dto: CreateDocumentDto, isWikiHome = false) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId: dto.organizationId, organizationId: dto.organizationId,
wikiId: dto.wikiId, wikiId: dto.wikiId,
@ -375,7 +305,11 @@ export class DocumentService {
} }
const document = await this.documentRepo.save(await this.documentRepo.create(data)); const document = await this.documentRepo.save(await this.documentRepo.create(data));
const { data: userAuths } = await this.authService.getUsersAuthInWiki(document.organizationId, document.wikiId); const { data: userAuths } = await this.authService.getUsersAuthInWiki(
document.organizationId,
document.wikiId,
null
);
await Promise.all([ await Promise.all([
...userAuths ...userAuths
@ -388,7 +322,7 @@ export class DocumentService {
documentId: document.id, documentId: document.id,
}); });
}), }),
await this.authService.createOrUpdateAuth(user.id, { this.authService.createOrUpdateAuth(user.id, {
auth: AuthEnum.creator, auth: AuthEnum.creator,
organizationId: document.organizationId, organizationId: document.organizationId,
wikiId: document.wikiId, wikiId: document.wikiId,
@ -399,26 +333,27 @@ export class DocumentService {
return instanceToPlain(document); return instanceToPlain(document);
} }
// /** /**
// * 删除知识库下所有文档 *
// * @param user * @param user
// * @param wikiId * @param wikiId
// */ */
// async deleteWikiDocuments(user, wikiId) { async deleteWikiDocuments(user, wikiId) {
// const docs = await this.documentRepo.find({ wikiId }); const docs = await this.documentRepo.find({ wikiId });
// await Promise.all( await Promise.all(
// docs.map((doc) => { docs.map((doc) => {
// return this.deleteDocument(user, doc.id); return this.deleteDocument(user, doc.id);
// }) })
// ); );
// } }
/** /**
* *
* @param idd * @param idd
*/ */
async deleteDocument(user: OutUser, documentId) { async deleteDocument(user: IUser, documentId) {
const document = await this.documentRepo.findOne(documentId); const document = await this.documentRepo.findOne(documentId);
if (document.isWikiHome) { if (document.isWikiHome) {
const isWikiExist = await this.wikiService.findById(document.wikiId); const isWikiExist = await this.wikiService.findById(document.wikiId);
if (isWikiExist) { if (isWikiExist) {
@ -435,6 +370,7 @@ export class DocumentService {
const children = await this.documentRepo.find({ const children = await this.documentRepo.find({
parentDocumentId: document.id, parentDocumentId: document.id,
}); });
if (children && children.length) { if (children && children.length) {
const parentDocumentId = document.parentDocumentId; const parentDocumentId = document.parentDocumentId;
await Promise.all( await Promise.all(
@ -448,11 +384,10 @@ export class DocumentService {
); );
} }
// TODO权限删除 await Promise.all([
// const auths = await this.documentUserRepo.find({ documentId }); this.authService.deleteDocument(document.organizationId, document.wikiId, document.id),
// await this.documentUserRepo.remove(auths); this.documentRepo.remove(document),
]);
return this.documentRepo.remove(document);
} }
/** /**
@ -462,7 +397,7 @@ export class DocumentService {
* @param dto * @param dto
* @returns * @returns
*/ */
public async updateDocument(user: OutUser, documentId: string, dto: UpdateDocumentDto) { public async updateDocument(user: IUser, documentId: string, dto: UpdateDocumentDto) {
const document = await this.documentRepo.findOne(documentId); const document = await this.documentRepo.findOne(documentId);
await this.authService.canEdit(user.id, { await this.authService.canEdit(user.id, {
@ -511,13 +446,40 @@ export class DocumentService {
}; };
} }
/**
*
* @param userId
* @param documentId
* @returns
*/
public async getDocumentUserAuth(userId, documentId) {
const document = await this.documentRepo.findOne(documentId);
const authority = await this.authService.getAuth(userId, {
organizationId: document.organizationId,
wikiId: document.wikiId,
documentId: document.id,
});
return {
...authority,
readable: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member].includes(authority.auth),
editable: [AuthEnum.creator, AuthEnum.admin].includes(authority.auth),
};
}
/** /**
* *
* @param user * @param user
* @param documentId * @param documentId
* @returns * @returns
*/ */
public async getDocumentVersion(user: OutUser, documentId: string) { public async getDocumentVersion(user: IUser, documentId: string) {
const document = await this.documentRepo.findOne(documentId);
await this.authService.canView(user.id, {
organizationId: document.organizationId,
wikiId: document.wikiId,
documentId: document.id,
});
const data = await this.documentVersionService.getDocumentVersions(documentId); const data = await this.documentVersionService.getDocumentVersions(documentId);
return data; return data;
} }
@ -526,7 +488,7 @@ export class DocumentService {
* *
* @param id * @param id
*/ */
async shareDocument(user: OutUser, documentId, dto: ShareDocumentDto, nextStatus = null) { async shareDocument(user: IUser, documentId, dto: ShareDocumentDto, nextStatus = null) {
const document = await this.documentRepo.findOne(documentId); const document = await this.documentRepo.findOne(documentId);
await this.authService.canEdit(user.id, { await this.authService.canEdit(user.id, {
organizationId: document.organizationId, organizationId: document.organizationId,
@ -570,7 +532,6 @@ export class DocumentService {
]); ]);
// 异步创建 // 异步创建
this.viewService.create(null, document); this.viewService.create(null, document);
return { ...doc, views, wiki, createUser }; return { ...doc, views, wiki, createUser };
} }
@ -581,7 +542,7 @@ export class DocumentService {
* @returns * @returns
*/ */
public async getChildrenDocuments( public async getChildrenDocuments(
user: OutUser, user: IUser,
data: { data: {
wikiId: string; wikiId: string;
documentId?: string; documentId?: string;
@ -694,7 +655,7 @@ export class DocumentService {
* @param dto * @param dto
* @returns * @returns
*/ */
public async getRecentDocuments(user: OutUser, organizationId) { public async getRecentDocuments(user: IUser, organizationId) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId: organizationId, organizationId: organizationId,
wikiId: null, wikiId: null,
@ -744,24 +705,25 @@ export class DocumentService {
.createQueryBuilder('document') .createQueryBuilder('document')
.andWhere('document.organizationId = :organizationId') .andWhere('document.organizationId = :organizationId')
.andWhere('document.title LIKE :keyword') .andWhere('document.title LIKE :keyword')
// FIXME: 编辑器内容的 json 字段可能也被匹配
.orWhere('document.content LIKE :keyword') .orWhere('document.content LIKE :keyword')
.setParameter('organizationId', organizationId) .setParameter('organizationId', organizationId)
.setParameter('keyword', `%${keyword}%`) .setParameter('keyword', `%${keyword}%`)
.getMany(); .getMany();
// const ret = await Promise.all( const ret = await Promise.all(
// res.map(async (doc) => { res.map(async (doc) => {
// const auth = await this.documentUserRepo.findOne({ const auth = await this.authService.getAuth(user.Id, {
// documentId: doc.id, organizationId: doc.organizationId,
// userId: user.id, wikiId: doc.wikiId,
// }); documentId: doc.id,
// return auth && auth.readable ? doc : null; });
// })
// );
// const data = ret.filter(Boolean); return auth && [AuthEnum.creator, AuthEnum.admin, AuthEnum.member].includes(auth.auth) ? doc : null;
})
);
return res; const data = ret.filter(Boolean);
return data;
} }
} }

View File

@ -1,7 +1,7 @@
import { MessageEntity } from '@entities/message.entity'; import { MessageEntity } from '@entities/message.entity';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { OutUser } from '@services/user.service'; import { IUser } from '@think/domains';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@Injectable() @Injectable()
@ -17,8 +17,8 @@ export class MessageService {
* @param msg * @param msg
* @returns * @returns
*/ */
async notify(user: OutUser, msg) { async notify(userId: IUser['id'], msg) {
const data = { userId: user.id, ...msg }; const data = { userId, ...msg };
const res = await this.messageRepo.create(data); const res = await this.messageRepo.create(data);
const ret = await this.messageRepo.save(res); const ret = await this.messageRepo.save(res);
return ret; return ret;

View File

@ -1,6 +1,5 @@
import { OperateUserAuthDto } from '@dtos/auth.dto'; import { OperateUserAuthDto } from '@dtos/auth.dto';
import { CreateOrganizationDto } from '@dtos/organization.dto'; import { CreateOrganizationDto } from '@dtos/organization.dto';
// import { OperateUserAuthDto } from '@dtos/organization-user.dto';
import { OrganizationEntity } from '@entities/organization.entity'; import { OrganizationEntity } from '@entities/organization.entity';
import { UserEntity } from '@entities/user.entity'; import { UserEntity } from '@entities/user.entity';
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
@ -8,6 +7,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { AuthService } from '@services/auth.service'; import { AuthService } from '@services/auth.service';
import { MessageService } from '@services/message.service'; import { MessageService } from '@services/message.service';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { WikiService } from '@services/wiki.service';
import { AuthEnum, buildMessageURL, IOrganization, IUser } from '@think/domains'; import { AuthEnum, buildMessageURL, IOrganization, IUser } from '@think/domains';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -24,7 +24,10 @@ export class OrganizationService {
private readonly userService: UserService, private readonly userService: UserService,
@Inject(forwardRef(() => MessageService)) @Inject(forwardRef(() => MessageService))
private readonly messageService: MessageService private readonly messageService: MessageService,
@Inject(forwardRef(() => WikiService))
private readonly wikiService: WikiService
) {} ) {}
public async findById(id: string) { public async findById(id: string) {
@ -90,6 +93,27 @@ export class OrganizationService {
return await this.organizationRepo.save(await this.organizationRepo.merge(oldData, dto)); return await this.organizationRepo.save(await this.organizationRepo.merge(oldData, dto));
} }
/**
*
* @param user
* @param organizationId
* @returns
*/
async deleteOrganization(user: IUser, organizationId) {
const organization = await this.organizationRepo.findOne(organizationId);
await this.authService.canDelete(user.id, {
organizationId: organization.id,
wikiId: null,
documentId: null,
});
await Promise.all([
this.authService.deleteOrganization(organization.id),
this.organizationRepo.remove(organization),
this.wikiService.deleteOrganizationWiki(user, organizationId),
]);
return organization;
}
/** /**
* *
* @param user * @param user
@ -101,7 +125,7 @@ export class OrganizationService {
} }
/** /**
* 访 * 访
* @param user * @param user
*/ */
public async getUserOrganizations(user: IUser) { public async getUserOrganizations(user: IUser) {
@ -130,7 +154,7 @@ export class OrganizationService {
* @param shortId * @param shortId
* @returns * @returns
*/ */
public async getMembers(user: IUser, id: IOrganization['id']) { public async getMembers(user: IUser, id: IOrganization['id'], pagination) {
const organization = await this.organizationRepo.findOne({ id }); const organization = await this.organizationRepo.findOne({ id });
if (!organization) { if (!organization) {
@ -143,7 +167,7 @@ export class OrganizationService {
documentId: null, documentId: null,
}); });
const { data: usersAuth, total } = await this.authService.getUsersAuthInOrganization(organization.id); const { data: usersAuth, total } = await this.authService.getUsersAuthInOrganization(organization.id, pagination);
const userIds = usersAuth.map((auth) => auth.userId); const userIds = usersAuth.map((auth) => auth.userId);
const users = await this.userService.findByIds(userIds); const users = await this.userService.findByIds(userIds);
@ -185,7 +209,7 @@ export class OrganizationService {
documentId: null, documentId: null,
}); });
await this.messageService.notify(targetUser, { await this.messageService.notify(targetUser.id, {
title: `您被添加到组织「${organization.name}`, title: `您被添加到组织「${organization.name}`,
message: `您被添加到知识库「${organization.name}」,快去看看吧!`, message: `您被添加到知识库「${organization.name}」,快去看看吧!`,
url: buildMessageURL('toOrganization')({ url: buildMessageURL('toOrganization')({
@ -223,7 +247,7 @@ export class OrganizationService {
documentId: null, documentId: null,
}); });
await this.messageService.notify(targetUser, { await this.messageService.notify(targetUser.id, {
title: `组织「${organization.name}」权限变更`, title: `组织「${organization.name}」权限变更`,
message: `您在组织「${organization.name}」权限已变更,快去看看吧!`, message: `您在组织「${organization.name}」权限已变更,快去看看吧!`,
url: buildMessageURL('toOrganization')({ url: buildMessageURL('toOrganization')({
@ -261,9 +285,9 @@ export class OrganizationService {
documentId: null, documentId: null,
}); });
await this.messageService.notify(targetUser, { await this.messageService.notify(targetUser.id, {
title: `组织「${organization.name}」权限收回`, title: `组织「${organization.name}」权限收回`,
message: `您在组织「${organization.name}」权限已收回,快去看看吧`, message: `您在组织「${organization.name}」权限已收回`,
url: buildMessageURL('toOrganization')({ url: buildMessageURL('toOrganization')({
organizationId: organization.id, organizationId: organization.id,
}), }),

View File

@ -5,9 +5,9 @@ import { InjectRepository } from '@nestjs/typeorm';
import { AuthService } from '@services/auth.service'; import { AuthService } from '@services/auth.service';
import { DocumentService } from '@services/document.service'; import { DocumentService } from '@services/document.service';
import { OrganizationService } from '@services/organization.service'; import { OrganizationService } from '@services/organization.service';
import { OutUser, UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { WikiService } from '@services/wiki.service'; import { WikiService } from '@services/wiki.service';
import { IDocument } from '@think/domains'; import { IDocument, IUser } from '@think/domains';
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -39,7 +39,7 @@ export class StarService {
* @param dto * @param dto
* @returns * @returns
*/ */
async toggleStar(user: OutUser, dto: StarDto) { async toggleStar(user: IUser, dto: StarDto) {
const data = { const data = {
...dto, ...dto,
userId: user.id, userId: user.id,
@ -61,7 +61,7 @@ export class StarService {
* @param dto * @param dto
* @returns * @returns
*/ */
async isStared(user: OutUser, dto: StarDto) { async isStared(user: IUser, dto: StarDto) {
const res = await this.starRepo.findOne({ userId: user.id, ...dto }); const res = await this.starRepo.findOne({ userId: user.id, ...dto });
return Boolean(res); return Boolean(res);
} }
@ -71,7 +71,7 @@ export class StarService {
* @param user * @param user
* @returns * @returns
*/ */
async getStarWikisInOrganization(user: OutUser, organizationId) { async getStarWikisInOrganization(user: IUser, organizationId) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId: organizationId, organizationId: organizationId,
wikiId: null, wikiId: null,
@ -100,7 +100,7 @@ export class StarService {
* @param user * @param user
* @returns * @returns
*/ */
async getStarDocumentsInWiki(user: OutUser, dto: StarDto) { async getStarDocumentsInWiki(user: IUser, dto: StarDto) {
const records = await this.starRepo.find({ const records = await this.starRepo.find({
userId: user.id, userId: user.id,
wikiId: dto.wikiId, wikiId: dto.wikiId,
@ -114,7 +114,7 @@ export class StarService {
const createUser = await this.userService.findById(doc.createUserId); const createUser = await this.userService.findById(doc.createUserId);
return { createUser, ...doc }; return { createUser, ...doc };
}) })
)) as Array<IDocument & { createUser: OutUser }>; )) as Array<IDocument & { createUser: IUser }>;
return withCreateUserRes return withCreateUserRes
.map((document) => { .map((document) => {
@ -134,7 +134,7 @@ export class StarService {
* @param user * @param user
* @returns * @returns
*/ */
async getStarDocumentsInOrganization(user: OutUser, organizationId) { async getStarDocumentsInOrganization(user: IUser, organizationId) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId: organizationId, organizationId: organizationId,
wikiId: null, wikiId: null,

View File

@ -40,21 +40,18 @@ export class SystemService {
*/ */
private async loadFromConfigFile() { private async loadFromConfigFile() {
const currentConfig = await this.getConfigFromDatabase(); const currentConfig = await this.getConfigFromDatabase();
const emailConfigKeys = ['emailServiceHost', 'emailServicePort', 'emailServiceUser', 'emailServicePassword'];
if (currentConfig && emailConfigKeys.every((configKey) => Boolean(currentConfig[configKey]))) {
return;
}
// 同步邮件服务配置 // 同步邮件服务配置
const emailConfigFromConfigFile = await this.confifgService.get('server.email'); const emailConfigFromConfigFile = await this.confifgService.get('server.email');
let emailConfig = {}; let emailConfig = {};
if (emailConfigFromConfigFile && typeof emailConfigFromConfigFile === 'object') { if (emailConfigFromConfigFile && typeof emailConfigFromConfigFile === 'object') {
emailConfig = { emailConfig = {
emailServiceHost: emailConfigFromConfigFile.host, emailServiceHost: currentConfig ? currentConfig.emailServiceHost : emailConfigFromConfigFile.host,
emailServicePort: emailConfigFromConfigFile.port, emailServicePort: currentConfig ? currentConfig.emailServicePort : emailConfigFromConfigFile.port,
emailServiceUser: emailConfigFromConfigFile.user, emailServiceUser: currentConfig ? currentConfig.emailServiceUser : emailConfigFromConfigFile.user,
emailServicePassword: emailConfigFromConfigFile.password, emailServicePassword: currentConfig ? currentConfig.emailServicePassword : emailConfigFromConfigFile.password,
}; };
} }
@ -118,4 +115,9 @@ export class SystemService {
); );
}); });
} }
async getPublicConfig() {
const config = await this.getConfigFromDatabase();
return { isSystemLocked: config.isSystemLocked, enableEmailVerify: config.enableEmailVerify };
}
} }

View File

@ -2,7 +2,8 @@ import { TemplateDto } from '@dtos/template.dto';
import { TemplateEntity } from '@entities/template.entity'; import { TemplateEntity } from '@entities/template.entity';
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { OutUser, UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { IUser } from '@think/domains';
import { instanceToPlain } from 'class-transformer'; import { instanceToPlain } from 'class-transformer';
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -45,7 +46,7 @@ export class TemplateService {
* @param dto * @param dto
* @returns * @returns
*/ */
async create(user: OutUser, dto: TemplateDto) { async create(user: IUser, dto: TemplateDto) {
const data = { const data = {
createUserId: user.id, createUserId: user.id,
...dto, ...dto,
@ -103,7 +104,7 @@ export class TemplateService {
* @param templateId * @param templateId
* @returns * @returns
*/ */
async useTemplate(user: OutUser, templateId) { async useTemplate(user: IUser, templateId) {
const data = await this.templateRepo.findOne(templateId); const data = await this.templateRepo.findOne(templateId);
if (user.id !== data.createUserId && !data.isPublic) { if (user.id !== data.createUserId && !data.isPublic) {
throw new HttpException('您不是模板创建者,无法编辑', HttpStatus.FORBIDDEN); throw new HttpException('您不是模板创建者,无法编辑', HttpStatus.FORBIDDEN);
@ -146,7 +147,7 @@ export class TemplateService {
* @param queryParams * @param queryParams
* @returns * @returns
*/ */
async getOwnTemplates(user: OutUser, queryParams) { async getOwnTemplates(user: IUser, queryParams) {
const query = this.templateRepo const query = this.templateRepo
.createQueryBuilder('template') .createQueryBuilder('template')
.where('template.createUserId=:createUserId') .where('template.createUserId=:createUserId')

View File

@ -18,8 +18,6 @@ import { IUser, UserStatus } from '@think/domains';
import { instanceToPlain } from 'class-transformer'; import { instanceToPlain } from 'class-transformer';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
export type OutUser = Omit<UserEntity, 'comparePassword' | 'encryptPassword' | 'encrypt' | 'password'>;
@Injectable() @Injectable()
export class UserService { export class UserService {
constructor( constructor(
@ -95,9 +93,9 @@ export class UserService {
* @param id * @param id
* @returns * @returns
*/ */
async findById(id): Promise<OutUser> { async findById(id): Promise<IUser> {
const user = await this.userRepo.findOne(id); const user = await this.userRepo.findOne(id);
return instanceToPlain(user) as OutUser; return instanceToPlain(user) as IUser;
} }
/** /**
@ -105,7 +103,7 @@ export class UserService {
* @param opts * @param opts
* @returns * @returns
*/ */
async findOne(opts: Partial<OutUser>): Promise<UserEntity> { async findOne(opts: Partial<UserEntity>): Promise<UserEntity> {
const user = await this.userRepo.findOne(opts); const user = await this.userRepo.findOne(opts);
return user; return user;
} }
@ -115,9 +113,9 @@ export class UserService {
* @param id * @param id
* @returns * @returns
*/ */
async findByIds(ids): Promise<OutUser[]> { async findByIds(ids): Promise<IUser[]> {
const users = await this.userRepo.findByIds(ids); const users = await this.userRepo.findByIds(ids);
return users.map((user) => instanceToPlain(user)) as OutUser[]; return users.map((user) => instanceToPlain(user)) as IUser[];
} }
/** /**
@ -125,7 +123,7 @@ export class UserService {
* @param user CreateUserDto * @param user CreateUserDto
* @returns * @returns
*/ */
async createUser(user: RegisterUserDto): Promise<OutUser> { async createUser(user: RegisterUserDto): Promise<IUser> {
const currentSystemConfig = await this.systemService.getConfigFromDatabase(); const currentSystemConfig = await this.systemService.getConfigFromDatabase();
if (currentSystemConfig.isSystemLocked) { if (currentSystemConfig.isSystemLocked) {
@ -144,7 +142,10 @@ export class UserService {
throw new HttpException('该邮箱已被注册', HttpStatus.BAD_REQUEST); throw new HttpException('该邮箱已被注册', HttpStatus.BAD_REQUEST);
} }
if (!(await this.verifyService.checkVerifyCode(user.email, user.verifyCode))) { if (
currentSystemConfig.enableEmailVerify &&
!(await this.verifyService.checkVerifyCode(user.email, user.verifyCode))
) {
throw new HttpException('验证码不正确,请检查', HttpStatus.BAD_REQUEST); throw new HttpException('验证码不正确,请检查', HttpStatus.BAD_REQUEST);
} }
@ -158,20 +159,7 @@ export class UserService {
isPersonal: true, isPersonal: true,
}); });
// const wiki = await this.wikiService.createWiki(createdUser, { return instanceToPlain(createdUser) as IUser;
// name: createdUser.name,
// description: `${createdUser.name}的个人空间`,
// });
// await this.starService.toggleStar(createdUser, {
// wikiId: wiki.id,
// });
// await this.messageService.notify(createdUser, {
// title: `欢迎「${createdUser.name}」`,
// message: `系统已自动为您创建知识库,快去看看吧!`,
// url: `/wiki/${wiki.id}`,
// });
return instanceToPlain(createdUser) as OutUser;
} }
/** /**
@ -213,7 +201,7 @@ export class UserService {
* @param user * @param user
* @returns * @returns
*/ */
async login(user: LoginUserDto): Promise<{ user: OutUser; token: string; domain: string; expiresIn: number }> { async login(user: LoginUserDto): Promise<{ user: IUser; token: string; domain: string; expiresIn: number }> {
const currentSystemConfig = await this.systemService.getConfigFromDatabase(); const currentSystemConfig = await this.systemService.getConfigFromDatabase();
const { name, password } = user; const { name, password } = user;
@ -237,7 +225,7 @@ export class UserService {
throw new HttpException('用户已锁定,无法登录', HttpStatus.BAD_REQUEST); throw new HttpException('用户已锁定,无法登录', HttpStatus.BAD_REQUEST);
} }
const res = instanceToPlain(existUser) as OutUser; const res = instanceToPlain(existUser) as IUser;
const token = this.jwtService.sign(res); const token = this.jwtService.sign(res);
const domain = this.confifgService.get('client.siteDomain'); const domain = this.confifgService.get('client.siteDomain');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -261,11 +249,11 @@ export class UserService {
* @param dto * @param dto
* @returns * @returns
*/ */
async updateUser(user: UserEntity, dto: UpdateUserDto): Promise<OutUser> { async updateUser(user: UserEntity, dto: UpdateUserDto): Promise<IUser> {
const oldData = await this.userRepo.findOne(user.id); const oldData = await this.userRepo.findOne(user.id);
const res = await this.userRepo.merge(oldData, dto); const res = await this.userRepo.merge(oldData, dto);
const ret = await this.userRepo.save(res); const ret = await this.userRepo.save(res);
return instanceToPlain(ret) as OutUser; return instanceToPlain(ret) as IUser;
} }
async decodeToken(token) { async decodeToken(token) {

View File

@ -1,54 +1,49 @@
import { VerifyEntity } from '@entities/verify.entity'; import { RedisDBEnum } from '@constants/*';
import { buildRedis } from '@helpers/redis.helper';
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { SystemService } from '@services/system.service'; import { SystemService } from '@services/system.service';
import Redis from 'ioredis';
import { randomInt } from 'node:crypto'; import { randomInt } from 'node:crypto';
import { Repository } from 'typeorm';
import { isEmail } from 'validator'; import { isEmail } from 'validator';
@Injectable() @Injectable()
export class VerifyService { export class VerifyService {
constructor( private redis: Redis;
@InjectRepository(VerifyEntity)
private readonly verifyRepo: Repository<VerifyEntity>,
constructor(
@Inject(forwardRef(() => SystemService)) @Inject(forwardRef(() => SystemService))
private readonly systemService: SystemService private readonly systemService: SystemService
) {} ) {
this.buildRedis();
}
/** private async buildRedis() {
* try {
* @param record this.redis = await buildRedis(RedisDBEnum.verify);
*/ console.log('[think] 验证码服务启动成功');
private async deleteVerifyCode(id) { } catch (e) {
const record = await this.verifyRepo.findOne(id); console.error(`[think] 验证码服务启动错误: "${e.message}"`);
await this.verifyRepo.remove(record); }
} }
/** /**
* *
* @param email * @param email
*/ */
public async sendVerifyCode(email: string) { public sendVerifyCode = async (email: string) => {
if (!email || !isEmail(email)) { if (!email || !isEmail(email)) {
throw new HttpException('请检查邮箱地址', HttpStatus.BAD_REQUEST); throw new HttpException('请检查邮箱地址', HttpStatus.BAD_REQUEST);
} }
const verifyCode = randomInt(1000000).toString().padStart(6, '0'); const verifyCode = randomInt(1000000).toString().padStart(6, '0');
const record = await this.verifyRepo.save(await this.verifyRepo.create({ email, verifyCode }));
console.log(verifyCode);
await this.redis.set(`verify-${email}`, verifyCode, 'EX', 10);
await this.systemService.sendEmail({ await this.systemService.sendEmail({
to: email, to: email,
subject: '验证码', subject: '验证码',
html: `<p>您的验证码为 ${verifyCode}</p>`, html: `<p>您的验证码为 ${verifyCode}</p>`,
}); });
};
const timer = setTimeout(() => {
this.deleteVerifyCode(record.id);
clearTimeout(timer);
}, 5 * 60 * 1000);
}
/** /**
* *
@ -56,17 +51,21 @@ export class VerifyService {
* @param verifyCode * @param verifyCode
* @returns * @returns
*/ */
public async checkVerifyCode(email: string, verifyCode: string) { public checkVerifyCode = async (email: string, verifyCode: string) => {
if (!email || !isEmail(email)) { if (!email || !isEmail(email)) {
throw new HttpException('请检查邮箱地址', HttpStatus.BAD_REQUEST); throw new HttpException('请检查邮箱地址', HttpStatus.BAD_REQUEST);
} }
const ret = await this.verifyRepo.findOne({ email, verifyCode }); const ret = await this.redis.get(`verify-${email}`);
if (!ret) { if (!ret) {
throw new HttpException('验证码已过期,请重新获取', HttpStatus.BAD_REQUEST);
}
if (ret !== verifyCode) {
throw new HttpException('验证码错误', HttpStatus.BAD_REQUEST); throw new HttpException('验证码错误', HttpStatus.BAD_REQUEST);
} }
return Boolean(ret); return Boolean(ret);
} };
} }

View File

@ -1,23 +1,17 @@
import { RedisDBEnum } from '@constants/*'; import { RedisDBEnum } from '@constants/*';
import { DocumentEntity } from '@entities/document.entity'; import { DocumentEntity } from '@entities/document.entity';
import { UserEntity } from '@entities/user.entity'; import { UserEntity } from '@entities/user.entity';
import { ViewEntity } from '@entities/view.entity';
import { buildRedis } from '@helpers/redis.helper'; import { buildRedis } from '@helpers/redis.helper';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IDocument, IOrganization, IUser } from '@think/domains'; import { IDocument, IOrganization, IUser } from '@think/domains';
import Redis from 'ioredis'; import Redis from 'ioredis';
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import { Repository } from 'typeorm';
@Injectable() @Injectable()
export class ViewService { export class ViewService {
private redis: Redis; private redis: Redis;
constructor( constructor() {
@InjectRepository(ViewEntity)
private readonly viewRepo: Repository<ViewEntity>
) {
this.buildRedis(); this.buildRedis();
} }

View File

@ -2,21 +2,17 @@ import { OperateUserAuthDto } from '@dtos/auth.dto';
import { CreateWikiDto } from '@dtos/create-wiki.dto'; import { CreateWikiDto } from '@dtos/create-wiki.dto';
import { ShareWikiDto } from '@dtos/share-wiki.dto'; import { ShareWikiDto } from '@dtos/share-wiki.dto';
import { UpdateWikiDto } from '@dtos/update-wiki.dto'; import { UpdateWikiDto } from '@dtos/update-wiki.dto';
// import { WikiUserDto } from '@dtos/wiki-user.dto';
import { WikiEntity } from '@entities/wiki.entity'; import { WikiEntity } from '@entities/wiki.entity';
// import { WikiUserEntity } from '@entities/wiki-user.entity';
import { array2tree } from '@helpers/tree.helper'; import { array2tree } from '@helpers/tree.helper';
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { AuthService } from '@services/auth.service'; import { AuthService } from '@services/auth.service';
import { DocumentService } from '@services/document.service'; import { DocumentService } from '@services/document.service';
import { MessageService } from '@services/message.service'; import { MessageService } from '@services/message.service';
import { OrganizationService } from '@services/organization.service';
import { StarService } from '@services/star.service'; import { StarService } from '@services/star.service';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { OutUser } from '@services/user.service';
import { ViewService } from '@services/view.service'; import { ViewService } from '@services/view.service';
import { AuthEnum, buildMessageURL, DocumentStatus, IPagination, WikiStatus, WikiUserRole } from '@think/domains'; import { AuthEnum, buildMessageURL, DocumentStatus, IPagination, IUser, WikiStatus } from '@think/domains';
import { instanceToPlain } from 'class-transformer'; import { instanceToPlain } from 'class-transformer';
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -30,9 +26,6 @@ export class WikiService {
@Inject(forwardRef(() => AuthService)) @Inject(forwardRef(() => AuthService))
private readonly authService: AuthService, private readonly authService: AuthService,
// @InjectRepository(WikiUserEntity)
// private readonly wikiUserRepo: Repository<WikiUserEntity>,
@Inject(forwardRef(() => MessageService)) @Inject(forwardRef(() => MessageService))
private readonly messageService: MessageService, private readonly messageService: MessageService,
@ -46,10 +39,7 @@ export class WikiService {
private readonly userService: UserService, private readonly userService: UserService,
@Inject(forwardRef(() => ViewService)) @Inject(forwardRef(() => ViewService))
private readonly viewService: ViewService, private readonly viewService: ViewService
@Inject(forwardRef(() => OrganizationService))
private readonly organizationService: OrganizationService
) {} ) {}
/** /**
@ -73,115 +63,6 @@ export class WikiService {
return ret; return ret;
} }
// /**
// * 目标用户是否为知识库成员
// * @param wikiId
// * @param userId
// * @returns
// */
// public async isMember(wikiId: string, userId: string) {
// const auth = await this.wikiUserRepo.findOne({ wikiId, userId });
// return !!auth && [WikiUserRole.admin, WikiUserRole.normal].includes(auth.userRole);
// }
// /**
// * 获取知识库成员信息
// * @param wikiId
// * @param userId
// * @returns
// */
// public async findWikiUser(wikiId: string, userId: string) {
// return await this.wikiUserRepo.findOne({
// userId,
// wikiId,
// });
// }
// /**
// * 操作知识库成员(添加、修改角色)
// * @param param0
// * @returns
// */
// async operateWikiUser({ wikiId, currentUserId, targetUserId, targetUserRole }) {
// const wiki = await this.wikiRepo.findOne(wikiId);
// // await this.organizationService.canUserVisitOrganization(currentUserId, wiki.organizationId);
// // 1. 检查知识库
// if (!wiki) {
// throw new HttpException('目标知识库不存在', HttpStatus.BAD_REQUEST);
// }
// const isCurrentUserCreator = currentUserId === wiki.createUserId;
// const isTargetUserCreator = targetUserId === wiki.createUserId;
// const currentWikiUserRole = isCurrentUserCreator
// ? WikiUserRole.admin
// : (
// await this.wikiUserRepo.findOne({
// wikiId: wiki.id,
// userId: currentUserId,
// })
// ).userRole;
// // 2. 检查成员是否存在
// const targetUser = await this.userService.findOne(targetUserId);
// const targetWikiUser = await this.wikiUserRepo.findOne({
// wikiId: wiki.id,
// userId: targetUserId,
// });
// if (targetWikiUser) {
// if (targetWikiUser.userRole === targetUserRole) return;
// // 2.1 修改知识库用户角色
// if (targetUserRole === WikiUserRole.admin) {
// if (currentWikiUserRole !== WikiUserRole.admin) {
// throw new HttpException('您无权限进行该操作', HttpStatus.FORBIDDEN);
// }
// }
// const userRole = isTargetUserCreator ? WikiUserRole.admin : targetUserRole;
// const newData = {
// ...targetWikiUser,
// userRole,
// };
// const res = await this.wikiUserRepo.merge(targetWikiUser, newData);
// const ret = await this.wikiUserRepo.save(res);
// await this.messageService.notify(targetUser, {
// title: `您在「${wiki.name}」的权限已变更`,
// message: `您在「${wiki.name}」的权限已变更,快去看看吧!`,
// url: buildMessageURL('toWiki')({
// organizationId: wiki.organizationId,
// wikiId: wiki.id,
// }),
// });
// return ret;
// } else {
// // 2.2. 添加知识库新用户
// if (currentWikiUserRole !== WikiUserRole.admin) {
// throw new HttpException('您无权限进行该操作', HttpStatus.FORBIDDEN);
// }
// const data: Partial<WikiUserEntity> = {
// wikiId,
// organizationId: wiki.organizationId,
// createUserId: wiki.createUserId,
// userId: targetUserId,
// userRole: isTargetUserCreator ? WikiUserRole.admin : targetUserRole,
// };
// const res = await this.wikiUserRepo.create(data);
// const ret = await this.wikiUserRepo.save(res);
// await this.messageService.notify(targetUser, {
// title: `您被添加到知识库「${wiki.name}」`,
// message: `您被添加到知识库「${wiki.name}」,快去看看吧!`,
// url: buildMessageURL('toWiki')({
// organizationId: wiki.organizationId,
// wikiId: wiki.id,
// }),
// });
// return ret;
// }
// }
/** /**
* *
* @param user * @param user
@ -189,7 +70,7 @@ export class WikiService {
* @param dto * @param dto
* @returns * @returns
*/ */
async addWikiUser(user: OutUser, wikiId, dto: OperateUserAuthDto) { async addWikiUser(user: IUser, wikiId, dto: OperateUserAuthDto) {
const targetUser = await this.userService.findOne({ name: dto.userName }); const targetUser = await this.userService.findOne({ name: dto.userName });
if (!targetUser) { if (!targetUser) {
@ -197,27 +78,40 @@ export class WikiService {
} }
const wiki = await this.wikiRepo.findOne(wikiId); const wiki = await this.wikiRepo.findOne(wikiId);
const homeDoc = await this.getWikiHomeDocument(user, wikiId);
if (!wiki) { if (!wiki) {
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND); throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
} }
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, { if (
auth: dto.userAuth, !(await this.authService.getAuth(targetUser.id, {
organizationId: wiki.organizationId, organizationId: wiki.organizationId,
wikiId: wiki.id, wikiId: null,
documentId: null, documentId: null,
}); }))
) {
throw new HttpException('该用户非组织成员', HttpStatus.FORBIDDEN);
}
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, { const homeDoc = await this.getWikiHomeDocument(user, wikiId);
auth: dto.userAuth,
organizationId: wiki.organizationId,
wikiId: wiki.id,
documentId: homeDoc.id,
});
await this.messageService.notify(targetUser, { await Promise.all([
this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
auth: dto.userAuth,
organizationId: wiki.organizationId,
wikiId: wiki.id,
documentId: null,
}),
this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
auth: dto.userAuth,
organizationId: wiki.organizationId,
wikiId: wiki.id,
documentId: homeDoc.id,
}),
]);
await this.messageService.notify(targetUser.id, {
title: `您被添加到知识库「${wiki.name}`, title: `您被添加到知识库「${wiki.name}`,
message: `您被添加到知识库「${wiki.name}」,快去看看吧!`, message: `您被添加到知识库「${wiki.name}」,快去看看吧!`,
url: buildMessageURL('toWiki')({ url: buildMessageURL('toWiki')({
@ -234,7 +128,7 @@ export class WikiService {
* @param dto * @param dto
* @returns * @returns
*/ */
async updateWikiUser(user: OutUser, wikiId, dto: OperateUserAuthDto) { async updateWikiUser(user: IUser, wikiId, dto: OperateUserAuthDto) {
const targetUser = await this.userService.findOne({ name: dto.userName }); const targetUser = await this.userService.findOne({ name: dto.userName });
if (!targetUser) { if (!targetUser) {
@ -248,21 +142,23 @@ export class WikiService {
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND); throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
} }
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, { await Promise.all([
auth: dto.userAuth, this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
organizationId: wiki.organizationId, auth: dto.userAuth,
wikiId: wiki.id, organizationId: wiki.organizationId,
documentId: null, wikiId: wiki.id,
}); documentId: null,
}),
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, { this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
auth: dto.userAuth, auth: dto.userAuth,
organizationId: wiki.organizationId, organizationId: wiki.organizationId,
wikiId: wiki.id, wikiId: wiki.id,
documentId: homeDoc.id, documentId: homeDoc.id,
}); }),
]);
await this.messageService.notify(targetUser, { await this.messageService.notify(targetUser.id, {
title: `您在知识库「${wiki.name}」的权限有更新`, title: `您在知识库「${wiki.name}」的权限有更新`,
message: `您在知识库「${wiki.name}」的权限有更新,快去看看吧!`, message: `您在知识库「${wiki.name}」的权限有更新,快去看看吧!`,
url: buildMessageURL('toWiki')({ url: buildMessageURL('toWiki')({
@ -279,7 +175,7 @@ export class WikiService {
* @param dto * @param dto
* @returns * @returns
*/ */
async deleteWikiUser(user: OutUser, wikiId, dto: OperateUserAuthDto) { async deleteWikiUser(user: IUser, wikiId, dto: OperateUserAuthDto) {
const targetUser = await this.userService.findOne({ name: dto.userName }); const targetUser = await this.userService.findOne({ name: dto.userName });
if (!targetUser) { if (!targetUser) {
@ -293,23 +189,25 @@ export class WikiService {
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND); throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
} }
await this.authService.deleteOtherUserAuth(user.id, targetUser.id, { await Promise.all([
auth: dto.userAuth, this.authService.deleteOtherUserAuth(user.id, targetUser.id, {
organizationId: wiki.organizationId, auth: dto.userAuth,
wikiId: wiki.id, organizationId: wiki.organizationId,
documentId: null, wikiId: wiki.id,
}); documentId: null,
}),
await this.authService.deleteOtherUserAuth(user.id, targetUser.id, { this.authService.deleteOtherUserAuth(user.id, targetUser.id, {
auth: dto.userAuth, auth: dto.userAuth,
organizationId: wiki.organizationId, organizationId: wiki.organizationId,
wikiId: wiki.id, wikiId: wiki.id,
documentId: homeDoc.id, documentId: homeDoc.id,
}); }),
]);
await this.messageService.notify(targetUser, { await this.messageService.notify(targetUser.id, {
title: `您在「${wiki.name}」的权限已变更`, title: `知识库「${wiki.name}」的权限收回`,
message: `您在${wiki.name}」的权限已变更,快去看看吧`, message: `您在知识库「${wiki.name}」的权限已收回`,
url: buildMessageURL('toWiki')({ url: buildMessageURL('toWiki')({
organizationId: wiki.organizationId, organizationId: wiki.organizationId,
wikiId: wiki.id, wikiId: wiki.id,
@ -322,7 +220,7 @@ export class WikiService {
* @param userId * @param userId
* @param wikiId * @param wikiId
*/ */
async getWikiUsers(user, wikiId) { async getWikiUsers(user, wikiId, pagination) {
const wiki = await this.wikiRepo.findOne(wikiId); const wiki = await this.wikiRepo.findOne(wikiId);
if (!wiki) { if (!wiki) {
@ -335,7 +233,11 @@ export class WikiService {
documentId: null, documentId: null,
}); });
const { data: usersAuth, total } = await this.authService.getUsersAuthInWiki(wiki.organizationId, wiki.id); const { data: usersAuth, total } = await this.authService.getUsersAuthInWiki(
wiki.organizationId,
wiki.id,
pagination
);
const userIds = usersAuth.map((auth) => auth.userId); const userIds = usersAuth.map((auth) => auth.userId);
const users = await this.userService.findByIds(userIds); const users = await this.userService.findByIds(userIds);
@ -350,28 +252,13 @@ export class WikiService {
return { data: withUserData, total }; return { data: withUserData, total };
} }
// /**
// * 查询知识库指定用户详情
// * @param workspaceId
// * @param userId
// * @returns
// */
// async getWikiUserDetail({ wikiId, userId }): Promise<WikiUserEntity> {
// const data = { wikiId, userId };
// const wikiUser = await this.wikiUserRepo.findOne(data);
// if (!wikiUser) {
// throw new HttpException('您不在该知识库中', HttpStatus.FORBIDDEN);
// }
// return wikiUser;
// }
/** /**
* *
* @param user * @param user
* @param dto CreateWikiDto * @param dto CreateWikiDto
* @returns * @returns
*/ */
async createWiki(user: OutUser, dto: CreateWikiDto) { async createWiki(user: IUser, dto: CreateWikiDto) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId: dto.organizationId, organizationId: dto.organizationId,
wikiId: null, wikiId: null,
@ -386,7 +273,7 @@ export class WikiService {
const toSaveWiki = await this.wikiRepo.create(data); const toSaveWiki = await this.wikiRepo.create(data);
const wiki = await this.wikiRepo.save(toSaveWiki); const wiki = await this.wikiRepo.save(toSaveWiki);
const { data: userAuths } = await this.authService.getUsersAuthInOrganization(wiki.organizationId); const { data: userAuths } = await this.authService.getUsersAuthInOrganization(wiki.organizationId, null);
await Promise.all([ await Promise.all([
...userAuths ...userAuths
@ -399,6 +286,7 @@ export class WikiService {
documentId: null, documentId: null,
}); });
}), }),
await this.authService.createOrUpdateAuth(user.id, { await this.authService.createOrUpdateAuth(user.id, {
auth: AuthEnum.creator, auth: AuthEnum.creator,
organizationId: wiki.organizationId, organizationId: wiki.organizationId,
@ -426,7 +314,6 @@ export class WikiService {
const homeDocumentId = homeDoc.id; const homeDocumentId = homeDoc.id;
const withHomeDocumentIdWiki = await this.wikiRepo.merge(wiki, { homeDocumentId }); const withHomeDocumentIdWiki = await this.wikiRepo.merge(wiki, { homeDocumentId });
await this.wikiRepo.save(withHomeDocumentIdWiki); await this.wikiRepo.save(withHomeDocumentIdWiki);
return withHomeDocumentIdWiki; return withHomeDocumentIdWiki;
} }
@ -436,7 +323,7 @@ export class WikiService {
* @param pagination * @param pagination
* @returns * @returns
*/ */
async getAllWikis(user: OutUser, organizationId, pagination: IPagination) { async getAllWikis(user: IUser, organizationId, pagination: IPagination) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId, organizationId,
wikiId: null, wikiId: null,
@ -467,7 +354,7 @@ export class WikiService {
* @param pagination * @param pagination
* @returns * @returns
*/ */
async getOwnWikis(user: OutUser, organizationId, pagination: IPagination) { async getOwnWikis(user: IUser, organizationId, pagination: IPagination) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId, organizationId,
wikiId: null, wikiId: null,
@ -497,7 +384,7 @@ export class WikiService {
* @param pagination * @param pagination
* @returns * @returns
*/ */
async getJoinWikis(user: OutUser, organizationId, pagination: IPagination) { async getJoinWikis(user: IUser, organizationId, pagination: IPagination) {
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId, organizationId,
wikiId: null, wikiId: null,
@ -528,7 +415,7 @@ export class WikiService {
* @param wikiId * @param wikiId
* @returns * @returns
*/ */
async getWikiDetail(user: OutUser, wikiId: string) { async getWikiDetail(user: IUser, wikiId: string) {
const wiki = await this.wikiRepo.findOne(wikiId); const wiki = await this.wikiRepo.findOne(wikiId);
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId: wiki.organizationId, organizationId: wiki.organizationId,
@ -545,7 +432,7 @@ export class WikiService {
* @param wikiId * @param wikiId
* @returns * @returns
*/ */
async getWikiHomeDocument(user: OutUser, wikiId) { async getWikiHomeDocument(user: IUser, wikiId) {
const res = await this.documentService.documentRepo.findOne({ wikiId, isWikiHome: true }); const res = await this.documentService.documentRepo.findOne({ wikiId, isWikiHome: true });
await this.authService.canView(user.id, { await this.authService.canView(user.id, {
organizationId: res.organizationId, organizationId: res.organizationId,
@ -562,7 +449,7 @@ export class WikiService {
* @param dto * @param dto
* @returns * @returns
*/ */
async updateWiki(user: OutUser, wikiId, dto: UpdateWikiDto) { async updateWiki(user: IUser, wikiId, dto: UpdateWikiDto) {
const oldData = await this.wikiRepo.findOne(wikiId); const oldData = await this.wikiRepo.findOne(wikiId);
if (!oldData) { if (!oldData) {
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND); throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
@ -586,26 +473,35 @@ export class WikiService {
* @param wikiId * @param wikiId
* @returns * @returns
*/ */
async deleteWiki(user: OutUser, wikiId) { async deleteWiki(user: IUser, wikiId) {
const wiki = await this.wikiRepo.findOne(wikiId); const wiki = await this.wikiRepo.findOne(wikiId);
await this.authService.canDelete(user.id, { await this.authService.canDelete(user.id, {
organizationId: wiki.organizationId, organizationId: wiki.organizationId,
wikiId: wiki.id, wikiId: wiki.id,
documentId: null, documentId: null,
}); });
await this.wikiRepo.remove(wiki); await Promise.all([
// TODO: 删除相应文档以及对应权限 this.authService.deleteWiki(wiki.organizationId, wiki.id),
// if (user.id !== wiki.createUserId) { this.wikiRepo.remove(wiki),
// throw new HttpException('您不是创建者,无法删除该知识库', HttpStatus.FORBIDDEN); this.documentService.deleteWikiDocuments(user, wikiId),
// } ]);
// await this.documentService.deleteWikiDocuments(user, wikiId);
// const users = await this.wikiUserRepo.find({ wikiId });
// await Promise.all([this.wikiUserRepo.remove(users)]);
return wiki; return wiki;
} }
/**
*
* @param user
* @param wikiId
*/
async deleteOrganizationWiki(user, organizationId) {
const wikis = await this.wikiRepo.find({ organizationId });
await Promise.all(
wikis.map((wiki) => {
return this.deleteWiki(user, wiki.id);
})
);
}
/** /**
* *
* @param user * @param user
@ -613,7 +509,7 @@ export class WikiService {
* @param dto * @param dto
* @returns * @returns
*/ */
async shareWiki(user: OutUser, wikiId, dto: ShareWikiDto) { async shareWiki(user: IUser, wikiId, dto: ShareWikiDto) {
const wiki = await this.wikiRepo.findOne(wikiId); const wiki = await this.wikiRepo.findOne(wikiId);
if (!wiki) { if (!wiki) {
@ -668,7 +564,7 @@ export class WikiService {
* @param wikiId * @param wikiId
* @returns * @returns
*/ */
async getWikiTocs(user: OutUser, wikiId) { async getWikiTocs(user: IUser, wikiId) {
const wiki = await this.wikiRepo.findOne(wikiId); const wiki = await this.wikiRepo.findOne(wikiId);
const records = await this.authService.getUserCanViewDocumentsInWiki(wiki.organizationId, wiki.id); const records = await this.authService.getUserCanViewDocumentsInWiki(wiki.organizationId, wiki.id);
@ -719,46 +615,6 @@ export class WikiService {
); );
} }
// /**
// * 获取知识库所有文档(无结构嵌套)
// * @param user
// * @param wikiId
// * @returns
// */
// async getWikiDocs(user: OutUser, wikiId) {
// // 通过文档成员表获取当前用户可查阅的所有文档
// const records = await this.documentService.documentUserRepo.find({
// userId: user.id,
// wikiId,
// });
// const ids = records.map((record) => record.documentId);
// const documents = await this.documentService.documentRepo.findByIds(ids);
// const docs = documents
// .filter((doc) => !doc.isWikiHome)
// .map((doc) => {
// const res = instanceToPlain(doc);
// res.key = res.id;
// return res;
// })
// .map((item) => {
// return lodash.omit(item, ['content', 'state']);
// });
// docs.sort((a, b) => a.index - b.index);
// const docsWithCreateUser = await Promise.all(
// docs.map(async (doc) => {
// const createUser = await this.userService.findById(doc.createUserId);
// return { ...doc, createUser };
// })
// );
// return docsWithCreateUser;
// }
/** /**
* *
* @param wikiId * @param wikiId