mirror of https://github.com/fantasticit/think.git
refactor: support org
parent
3e0eb5dc92
commit
808f7877eb
|
@ -23,6 +23,7 @@ server:
|
|||
enableRateLimit: true # 是否限流
|
||||
rateLimitWindowMs: 60000 # 限流时间
|
||||
rateLimitMax: 1000 # 单位限流时间内单个 ip 最大访问数量
|
||||
enableEmailVerify: false
|
||||
email: # 邮箱服务,参考 http://help.163.com/09/1223/14/5R7P6CJ600753VB8.html?servCode=6010376 获取 SMTP 配置
|
||||
host: ''
|
||||
port: 465
|
||||
|
|
|
@ -30,6 +30,7 @@ export const System = () => {
|
|||
<Banner type="warning" description="系统锁定后,除系统管理员外均不可登录,谨慎修改!" closeIcon={null} />
|
||||
<Form labelPosition="left" initValues={data} onChange={onFormChange} onSubmit={onFinish}>
|
||||
<Form.Switch field="isSystemLocked" label="系统锁定" />
|
||||
<Form.Switch field="enableEmailVerify" label="邮箱检验" />
|
||||
|
||||
<Button
|
||||
htmlType="submit"
|
||||
|
|
|
@ -20,7 +20,18 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
|||
const { user: currentUser } = useUser();
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
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(
|
||||
() => (
|
||||
<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}
|
||||
footer={null}
|
||||
onCancel={toggleVisible}
|
||||
style={{ maxWidth: '96vw' }}
|
||||
style={{ maxWidth: '96vw', maxHeight: '60vh', overflow: 'auto' }}
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
|
@ -97,6 +108,8 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
|||
width: 412,
|
||||
maxWidth: '96vw',
|
||||
padding: '0 24px',
|
||||
maxHeight: '60vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { useRouterQuery } from 'hooks/use-router-query';
|
||||
import Router from 'next/router';
|
||||
|
@ -15,8 +15,11 @@ interface IProps {
|
|||
const { Text } = Typography;
|
||||
|
||||
export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render, onDelete }) => {
|
||||
const { wikiId: currentWikiId, documentId: currentDocumentId } =
|
||||
useRouterQuery<{ wikiId?: string; documentId?: string }>();
|
||||
const {
|
||||
organizationId,
|
||||
wikiId: currentWikiId,
|
||||
documentId: currentDocumentId,
|
||||
} = useRouterQuery<{ organizationId: string; wikiId?: string; documentId?: string }>();
|
||||
const { deleteDocument: api, loading } = useDeleteDocument(documentId);
|
||||
|
||||
const deleteAction = useCallback(() => {
|
||||
|
@ -26,14 +29,15 @@ export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render,
|
|||
return;
|
||||
}
|
||||
Router.push({
|
||||
pathname: `/wiki/${wikiId}`,
|
||||
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
|
||||
query: { organizationId, wikiId },
|
||||
});
|
||||
};
|
||||
|
||||
navigate();
|
||||
onDelete && onDelete();
|
||||
});
|
||||
}, [wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]);
|
||||
}, [organizationId, wikiId, documentId, api, onDelete, currentWikiId, currentDocumentId]);
|
||||
|
||||
const content = useMemo(
|
||||
() => (
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
import { IAuthority, ILoginUser } from '@think/domains';
|
||||
import cls from 'classnames';
|
||||
import { useDoumentMembers } from 'data/document';
|
||||
import { event, triggerChangeDocumentTitle, triggerJoinUser, USE_DOCUMENT_VERSION } from 'event';
|
||||
import { useMount } from 'hooks/use-mount';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import Router from 'next/router';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { CollaborationEditor, ICollaborationRefProps } from 'tiptap/editor';
|
||||
import { findMentions } from 'tiptap/prose-utils';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import { DocumentUserSetting } from './users';
|
||||
|
||||
interface IProps {
|
||||
user: ILoginUser;
|
||||
|
@ -19,12 +14,8 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority }) => {
|
||||
const $hasShowUserSettingModal = useRef(false);
|
||||
const $editor = useRef<ICollaborationRefProps>();
|
||||
const mounted = useMount();
|
||||
const { users, addUser, updateUser } = useDoumentMembers(documentId);
|
||||
const [mentionUsersSettingVisible, toggleMentionUsersSettingVisible] = useToggle(false);
|
||||
const [mentionUsers, setMentionUsers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
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 (
|
||||
<div className={cls(styles.editorWrap)}>
|
||||
{mounted && (
|
||||
|
@ -103,14 +44,6 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
|
|||
onAwarenessUpdate={triggerJoinUser}
|
||||
/>
|
||||
)}
|
||||
<DocumentUserSetting
|
||||
visible={mentionUsersSettingVisible}
|
||||
toggleVisible={toggleMentionUsersSettingVisible}
|
||||
mentionUsers={mentionUsers}
|
||||
users={users}
|
||||
addUser={addUser}
|
||||
updateUser={updateUser}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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 React, { useCallback, useState } from 'react';
|
||||
|
||||
interface IProps {
|
||||
visible: boolean;
|
||||
toggleVisible: (arg) => void;
|
||||
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 [userName, setUserName] = useState('');
|
||||
|
||||
|
@ -16,27 +14,26 @@ export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
|
|||
onOk({ userName, userAuth }).then(() => {
|
||||
setUserAuth(AuthEnum.noAccess);
|
||||
setUserName('');
|
||||
toggleVisible(false);
|
||||
});
|
||||
}, [onOk, userName, userAuth, toggleVisible]);
|
||||
}, [onOk, userName, userAuth]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<Popconfirm
|
||||
zIndex={1070}
|
||||
title={'添加成员'}
|
||||
okText={'邀请对方'}
|
||||
visible={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={() => toggleVisible(false)}
|
||||
maskClosable={false}
|
||||
style={{ maxWidth: '96vw' }}
|
||||
footer={null}
|
||||
>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
style={{ maxWidth: '96vw', width: 380 }}
|
||||
onConfirm={handleOk}
|
||||
okButtonProps={{
|
||||
disabled: !userName,
|
||||
}}
|
||||
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 }}>
|
||||
<Select value={userAuth} onChange={setUserAuth} style={{ width: 120 }} zIndex={1080}>
|
||||
{AuthEnumArray.map((wikiStatus) => {
|
||||
return (
|
||||
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
|
||||
|
@ -50,13 +47,13 @@ export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
|
|||
placeholder="输入对方用户名"
|
||||
value={userName}
|
||||
onChange={setUserName}
|
||||
style={{ width: 270 }}
|
||||
style={{ width: 160 }}
|
||||
></Input>
|
||||
</Space>
|
||||
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk} disabled={!userName}>
|
||||
添加成员
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Popconfirm>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,41 +1,26 @@
|
|||
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 React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
interface IProps {
|
||||
visible: boolean;
|
||||
toggleVisible: (arg) => void;
|
||||
currentUser: { user: IUser; auth: IAuth };
|
||||
onOk: (arg) => any;
|
||||
userWithAuth: { user: IUser; auth: IAuth };
|
||||
updateUser: (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 handleOk = useCallback(() => {
|
||||
onOk(userAuth).then(() => {
|
||||
setUserAuth(AuthEnum.noAccess);
|
||||
toggleVisible(false);
|
||||
return updateUser({ userName: userWithAuth.user.name, userAuth }).then(() => {
|
||||
Toast.success('操作成功');
|
||||
});
|
||||
}, [onOk, userAuth, toggleVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
setUserAuth(AuthEnum.noAccess);
|
||||
}
|
||||
}, [visible]);
|
||||
}, [updateUser, userAuth, userWithAuth]);
|
||||
|
||||
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 }}>
|
||||
<Popconfirm
|
||||
title={`修改用户${userWithAuth && userWithAuth.user.name}权限`}
|
||||
content={
|
||||
<div style={{ margin: '16px -68px 0 0' }}>
|
||||
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
|
||||
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
|
||||
) : null}
|
||||
|
@ -49,10 +34,11 @@ export const EditUser: React.FC<IProps> = ({ visible, toggleVisible, currentUser
|
|||
);
|
||||
})}
|
||||
</Select>
|
||||
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk}>
|
||||
提交修改
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
onConfirm={handleOk}
|
||||
>
|
||||
{children}
|
||||
</Popconfirm>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
|
||||
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 { LocaleTime } from 'components/locale-time';
|
||||
import { useOrganizationMembers } from 'data/organization';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { AddUser } from './add';
|
||||
import { EditUser } from './edit';
|
||||
|
@ -14,33 +12,17 @@ import styles from './index.module.scss';
|
|||
interface IProps {
|
||||
id: string;
|
||||
hook: any;
|
||||
descriptions?: Array<string>;
|
||||
}
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
const { Column } = Table;
|
||||
|
||||
export const Members: React.FC<IProps> = ({ id, hook }) => {
|
||||
const { data, loading, error, 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);
|
||||
export const Members: React.FC<IProps> = ({ id, hook, descriptions }) => {
|
||||
const { data, loading, error, page, pageSize, setPage, addUser, updateUser, deleteUser } = hook(id);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<header>{/* <MemberAdder /> */}</header>
|
||||
<DataRender
|
||||
loading={loading}
|
||||
error={error}
|
||||
|
@ -55,19 +37,36 @@ export const Members: React.FC<IProps> = ({ id, hook }) => {
|
|||
title={<Title heading={6}>权限说明</Title>}
|
||||
description={
|
||||
<div>
|
||||
{descriptions && descriptions.length ? (
|
||||
descriptions.map((desc) => {
|
||||
return <Paragraph key={desc}>{desc}</Paragraph>;
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
<Paragraph>创建者:管理组织内所有知识库、文档,可删除组织</Paragraph>
|
||||
<Paragraph>管理员:管理组织内所有知识库、文档,不可删除组织</Paragraph>
|
||||
<Paragraph>成员:可访问组织内所有知识库、文档,不可删除组织</Paragraph>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button theme="solid" onClick={toggleVisible}>
|
||||
添加用户
|
||||
</Button>
|
||||
<AddUser onOk={addUser}>
|
||||
<Button theme="solid">添加用户</Button>
|
||||
</AddUser>
|
||||
</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="成员权限"
|
||||
|
@ -90,7 +89,9 @@ export const Members: React.FC<IProps> = ({ id, hook }) => {
|
|||
align="center"
|
||||
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
|
||||
showArrow
|
||||
title="确认删除该成员?"
|
||||
|
@ -105,9 +106,6 @@ export const Members: React.FC<IProps> = ({ id, hook }) => {
|
|||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<AddUser visible={visible} toggleVisible={toggleVisible} onOk={addUser} />
|
||||
<EditUser visible={editVisible} toggleVisible={toggleEditVisible} currentUser={currentUser} onOk={handleEdit} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -1,33 +1,23 @@
|
|||
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 { DataRender } from 'components/data-render';
|
||||
import { LogoImage, LogoText } from 'components/logo';
|
||||
import { useUserOrganizations } from 'data/organization';
|
||||
import { useUser } from 'data/user';
|
||||
import Link from 'next/link';
|
||||
import Router from 'next/router';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
export const OrganizationPublicSwitcher = () => {
|
||||
const Inner = () => {
|
||||
const {
|
||||
data: userOrganizations,
|
||||
loading: userOrganizationsLoading,
|
||||
error: userOrganizationsError,
|
||||
} = useUserOrganizations();
|
||||
|
||||
const gotoCreate = useCallback(() => {
|
||||
Router.push(`/app/org/create`);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<span className={styles.nameWrap}>
|
||||
<Space>
|
||||
<LogoImage />
|
||||
<LogoText />
|
||||
</Space>
|
||||
<Dropdown
|
||||
trigger="click"
|
||||
render={
|
||||
|
@ -37,7 +27,9 @@ export const OrganizationPublicSwitcher = () => {
|
|||
normalContent={() => {
|
||||
return (
|
||||
<Dropdown.Menu>
|
||||
{(userOrganizations || []).map((org) => {
|
||||
{userOrganizations.length ? (
|
||||
<>
|
||||
{userOrganizations.map((org) => {
|
||||
return (
|
||||
<Dropdown.Item key={org.id}>
|
||||
<Link
|
||||
|
@ -66,10 +58,44 @@ export const OrganizationPublicSwitcher = () => {
|
|||
);
|
||||
})}
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item onClick={gotoCreate}>
|
||||
</>
|
||||
) : null}
|
||||
<Dropdown.Item>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/',
|
||||
}}
|
||||
>
|
||||
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Text>
|
||||
<Space>新建组织</Space>
|
||||
<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>
|
||||
);
|
||||
|
@ -79,6 +105,19 @@ export const OrganizationPublicSwitcher = () => {
|
|||
>
|
||||
<Button size="small" icon={<IconSmallTriangleDown />} style={{ marginLeft: 12 }} />
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export const OrganizationPublicSwitcher = () => {
|
||||
const { user } = useUser();
|
||||
|
||||
return (
|
||||
<span className={styles.nameWrap}>
|
||||
<Space>
|
||||
<LogoImage />
|
||||
<LogoText />
|
||||
</Space>
|
||||
{user && <Inner />}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -36,29 +36,9 @@ export const OrganizationSetting: React.FC<IProps> = ({ organizationId, tab, onN
|
|||
<OrganizationMembers organizationId={organizationId} />
|
||||
</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">
|
||||
<More wikiId={wikiId} />
|
||||
</TabPane> */}
|
||||
<More organizationId={organizationId} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
.wrap {
|
||||
> header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
|
@ -1,110 +1,12 @@
|
|||
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
|
||||
import { Banner, Button, Popconfirm, Table, Typography } from '@douyinfe/semi-ui';
|
||||
import { AuthEnumTextMap, IOrganization } from '@think/domains';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { LocaleTime } from 'components/locale-time';
|
||||
import { IOrganization } from '@think/domains';
|
||||
import { Members } from 'components/members';
|
||||
import { useOrganizationMembers } from 'data/organization';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { AddUser } from './add';
|
||||
import { EditUser } from './edit';
|
||||
import styles from './index.module.scss';
|
||||
import React from 'react';
|
||||
|
||||
interface IProps {
|
||||
organizationId: IOrganization['id'];
|
||||
}
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
const { Column } = Table;
|
||||
|
||||
export const OrganizationMembers: React.FC<IProps> = ({ organizationId }) => {
|
||||
const { data, loading, error, refresh, addUser, updateUser, deleteUser } = useOrganizationMembers(organizationId);
|
||||
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>
|
||||
);
|
||||
return <Members id={organizationId} hook={useOrganizationMembers} />;
|
||||
};
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
import { Banner, Button, Typography } from '@douyinfe/semi-ui';
|
||||
import { WorkspaceDeletor } from 'components/wiki/delete';
|
||||
|
||||
interface IProps {
|
||||
wikiId: string;
|
||||
}
|
||||
import { OrganizationDeletor } from 'components/organization/delete';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
export const More: React.FC<IProps> = ({ wikiId }) => {
|
||||
export const More = ({ organizationId }) => {
|
||||
return (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Banner
|
||||
fullMode={false}
|
||||
type="danger"
|
||||
closeIcon={null}
|
||||
description={<Paragraph>删除知识库及内部所有文档,不可恢复!</Paragraph>}
|
||||
description={<Paragraph>删除组织及内部所有知识库以及文档,不可恢复!</Paragraph>}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<WorkspaceDeletor wikiId={wikiId}>
|
||||
<OrganizationDeletor organizationId={organizationId}>
|
||||
<Button type="danger" theme="solid">
|
||||
删除知识库
|
||||
删除组织
|
||||
</Button>
|
||||
</WorkspaceDeletor>
|
||||
</OrganizationDeletor>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 { Avatar } from '@douyinfe/semi-ui';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { useOrganizationDetail, useUserOrganizations } from 'data/organization';
|
||||
import { useRouterQuery } from 'hooks/use-router-query';
|
||||
import Link from 'next/link';
|
||||
import Router from 'next/router';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
|
@ -22,10 +20,6 @@ export const OrganizationSwitcher = () => {
|
|||
error: userOrganizationsError,
|
||||
} = useUserOrganizations();
|
||||
|
||||
const gotoCreate = useCallback(() => {
|
||||
Router.push(`/app/org/create`);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DataRender
|
||||
loading={loading}
|
||||
|
@ -64,7 +58,9 @@ export const OrganizationSwitcher = () => {
|
|||
normalContent={() => {
|
||||
return (
|
||||
<Dropdown.Menu>
|
||||
{(userOrganizations || []).map((org) => {
|
||||
{userOrganizations.length ? (
|
||||
<>
|
||||
{userOrganizations.map((org) => {
|
||||
return (
|
||||
<Dropdown.Item key={org.id}>
|
||||
<Link
|
||||
|
@ -93,7 +89,34 @@ export const OrganizationSwitcher = () => {
|
|||
);
|
||||
})}
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item onClick={gotoCreate}>
|
||||
</>
|
||||
) : 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">
|
||||
|
@ -102,6 +125,8 @@ export const OrganizationSwitcher = () => {
|
|||
新建组织
|
||||
</Space>
|
||||
</Text>
|
||||
</a>
|
||||
</Link>
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
);
|
||||
|
|
|
@ -27,6 +27,7 @@ export const WikiCreator: React.FC<IProps> = ({ visible, toggleVisible }) => {
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
toggleVisible(false);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { IconDelete } from '@douyinfe/semi-icons';
|
||||
import { Modal, Space, Typography } from '@douyinfe/semi-ui';
|
||||
import { useOwnWikis } from 'data/wiki';
|
||||
import { useRouterQuery } from 'hooks/use-router-query';
|
||||
import Router from 'next/router';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
|
@ -11,8 +12,9 @@ interface IProps {
|
|||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children }) => {
|
||||
const { deletWiki } = useOwnWikis();
|
||||
export const WikiDeletor: React.FC<IProps> = ({ wikiId, onDelete, children }) => {
|
||||
const { organizationId } = useRouterQuery<{ organizationId: string }>();
|
||||
const { deletWiki } = useOwnWikis(organizationId);
|
||||
|
||||
const deleteAction = useCallback(() => {
|
||||
Modal.error({
|
||||
|
@ -23,7 +25,8 @@ export const WorkspaceDeletor: React.FC<IProps> = ({ wikiId, onDelete, children
|
|||
onDelete
|
||||
? onDelete()
|
||||
: 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' },
|
||||
});
|
||||
}, [wikiId, deletWiki, onDelete]);
|
||||
}, [organizationId, wikiId, deletWiki, onDelete]);
|
||||
|
||||
return (
|
||||
<Text type="danger" onClick={deleteAction}>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Banner, Button, Typography } from '@douyinfe/semi-ui';
|
||||
import { WorkspaceDeletor } from 'components/wiki/delete';
|
||||
import { WikiDeletor } from 'components/wiki/delete';
|
||||
|
||||
interface IProps {
|
||||
wikiId: string;
|
||||
|
@ -17,11 +17,11 @@ export const More: React.FC<IProps> = ({ wikiId }) => {
|
|||
description={<Paragraph>删除知识库及内部所有文档,不可恢复!</Paragraph>}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<WorkspaceDeletor wikiId={wikiId}>
|
||||
<WikiDeletor wikiId={wikiId}>
|
||||
<Button type="danger" theme="solid">
|
||||
删除知识库
|
||||
</Button>
|
||||
</WorkspaceDeletor>
|
||||
</WikiDeletor>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,5 +7,11 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const Users: React.FC<IProps> = ({ wikiId }) => {
|
||||
return <Members id={wikiId} hook={useWikiMembers} />;
|
||||
return (
|
||||
<Members
|
||||
id={wikiId}
|
||||
hook={useWikiMembers}
|
||||
descriptions={['权限继承:默认继承组织成员权限', '超级管理员:组织超级管理员和知识库创建者']}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -65,11 +65,20 @@ export type DocAuth = {
|
|||
* @param cookie
|
||||
* @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({
|
||||
method: DocumentApiDefinition.getMemberById.method,
|
||||
url: DocumentApiDefinition.getMemberById.client(documentId),
|
||||
cookie,
|
||||
params: {
|
||||
page,
|
||||
pageSize,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -79,9 +88,11 @@ export const getDocumentMembers = (documentId, cookie = null): Promise<Array<{ u
|
|||
* @returns
|
||||
*/
|
||||
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(
|
||||
DocumentApiDefinition.getMemberById.client(documentId),
|
||||
() => getDocumentMembers(documentId),
|
||||
[DocumentApiDefinition.getMemberById.client(documentId), page],
|
||||
() => getDocumentMembers(documentId, page, pageSize),
|
||||
options
|
||||
);
|
||||
|
||||
|
@ -124,7 +135,7 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
|
|||
[refetch, documentId]
|
||||
);
|
||||
|
||||
return { data, loading: isLoading, error, addUser, updateUser, deleteUser };
|
||||
return { data, loading: isLoading, error, page, pageSize, setPage, addUser, updateUser, deleteUser };
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -108,17 +108,32 @@ export const useOrganizationDetail = (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 = (
|
||||
id,
|
||||
page = 1,
|
||||
pageSize,
|
||||
cookie = null
|
||||
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
|
||||
return HttpClient.request({
|
||||
method: OrganizationApiDefinition.getMembers.method,
|
||||
url: OrganizationApiDefinition.getMembers.client(id),
|
||||
cookie,
|
||||
params: {
|
||||
page,
|
||||
pageSize,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -127,8 +142,10 @@ export const getOrganizationMembers = (
|
|||
* @returns
|
||||
*/
|
||||
export const useOrganizationMembers = (id) => {
|
||||
const { data, error, isLoading, refetch } = useQuery(OrganizationApiDefinition.getMembers.client(id), () =>
|
||||
getOrganizationMembers(id)
|
||||
const [pageSize] = useState(12);
|
||||
const [page, setPage] = useState(1);
|
||||
const { data, error, isLoading, refetch } = useQuery([OrganizationApiDefinition.getMembers.client(id), page], () =>
|
||||
getOrganizationMembers(id, page, pageSize)
|
||||
);
|
||||
|
||||
const addUser = useCallback(
|
||||
|
@ -170,5 +187,16 @@ export const useOrganizationMembers = (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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { useAsyncLoading } from 'hooks/use-async-loading';
|
||||
import Router, { useRouter } from 'next/router';
|
||||
|
@ -138,10 +138,17 @@ export const useUser = () => {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
* @returns
|
||||
*/
|
||||
export const useSystemPublicConfig = () => {
|
||||
const { data, error, isLoading, refetch } = useQuery(SystemApiDefinition.getPublicConfig.client(), () =>
|
||||
HttpClient.request<ISystemConfig>({
|
||||
method: SystemApiDefinition.getPublicConfig.method,
|
||||
url: SystemApiDefinition.getPublicConfig.client(),
|
||||
})
|
||||
);
|
||||
|
||||
return { data, error, loading: isLoading, refresh: refetch };
|
||||
};
|
||||
|
||||
export const useSystemConfig = () => {
|
||||
const { data, error, isLoading, refetch } = useQuery(UserApiDefinition.getSystemConfig.client(), () =>
|
||||
HttpClient.request<ISystemConfig>({
|
||||
|
|
|
@ -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 { useCallback, useEffect, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
|
@ -97,7 +97,7 @@ export const useOwnWikis = (organizationId) => {
|
|||
);
|
||||
|
||||
/**
|
||||
* 删除文档
|
||||
* 删除知识库
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
|
@ -318,12 +318,18 @@ export const useWikiDocuments = (wikiId) => {
|
|||
*/
|
||||
export const getWikiMembers = (
|
||||
wikiId,
|
||||
page,
|
||||
pageSize,
|
||||
cookie = null
|
||||
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
|
||||
return HttpClient.request({
|
||||
method: WikiApiDefinition.getMemberById.method,
|
||||
url: WikiApiDefinition.getMemberById.client(wikiId),
|
||||
cookie,
|
||||
params: {
|
||||
page,
|
||||
pageSize,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -333,8 +339,10 @@ export const getWikiMembers = (
|
|||
* @returns
|
||||
*/
|
||||
export const useWikiMembers = (wikiId) => {
|
||||
const { data, error, isLoading, refetch } = useQuery(WikiApiDefinition.getMemberById.client(wikiId), () =>
|
||||
getWikiMembers(wikiId)
|
||||
const [pageSize] = useState(12);
|
||||
const [page, setPage] = useState(1);
|
||||
const { data, error, isLoading, refetch } = useQuery([WikiApiDefinition.getMemberById.client(wikiId), page], () =>
|
||||
getWikiMembers(wikiId, page, pageSize)
|
||||
);
|
||||
|
||||
const addUser = useCallback(
|
||||
|
@ -376,7 +384,7 @@ export const useWikiMembers = (wikiId) => {
|
|||
[refetch, wikiId]
|
||||
);
|
||||
|
||||
return { data, loading: isLoading, error, addUser, updateUser, deleteUser };
|
||||
return { data, loading: isLoading, error, page, pageSize, setPage, addUser, updateUser, deleteUser };
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 { OrganizationPublicSwitcher } from 'components/organization/public-switcher';
|
||||
import { Theme } from 'components/theme';
|
||||
import { User } from 'components/user';
|
||||
import { useUser } from 'data/user';
|
||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||
import Router, { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
const { Header: SemiHeader } = SemiLayout;
|
||||
|
||||
|
@ -28,12 +29,26 @@ const menus = [
|
|||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
itemKey: '/template',
|
||||
text: '模板',
|
||||
onClick: () => {
|
||||
Router.push({
|
||||
pathname: `/template`,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const RouterHeader: React.FC = () => {
|
||||
const { user } = useUser();
|
||||
const { pathname } = useRouter();
|
||||
const { isMobile } = IsOnMobile.useHook();
|
||||
|
||||
const gotoApp = useCallback(() => {
|
||||
Router.push(`/app`);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SemiHeader>
|
||||
{isMobile ? (
|
||||
|
@ -47,6 +62,11 @@ export const RouterHeader: React.FC = () => {
|
|||
}
|
||||
footer={
|
||||
<Space>
|
||||
{user && (
|
||||
<Button theme="solid" onClick={gotoApp}>
|
||||
前往组织空间
|
||||
</Button>
|
||||
)}
|
||||
<Theme />
|
||||
<User />
|
||||
</Space>
|
||||
|
@ -65,7 +85,12 @@ export const RouterHeader: React.FC = () => {
|
|||
items={menus}
|
||||
footer={
|
||||
<Space>
|
||||
<Message />
|
||||
{user && (
|
||||
<Button theme="solid" onClick={gotoApp}>
|
||||
前往组织空间
|
||||
</Button>
|
||||
)}
|
||||
{user && <Message />}
|
||||
<Theme />
|
||||
<User />
|
||||
</Space>
|
||||
|
|
|
@ -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 { Seo } from 'components/seo';
|
||||
import { useUser } from 'data/user';
|
||||
|
@ -37,6 +37,7 @@ const Page: NextPage = () => {
|
|||
管理后台
|
||||
</Title>
|
||||
</div>
|
||||
<Banner type="info" description="该部分是全局的系统管理后台,用于系统配置管理等操作!" />
|
||||
<SystemConfig tab={tab} onNavigate={navigate} />
|
||||
</>
|
||||
) : (
|
||||
|
|
|
@ -1,32 +1,103 @@
|
|||
import { Spin } from '@douyinfe/semi-ui';
|
||||
import { usePeronalOrganization } from 'data/organization';
|
||||
import { Avatar, Button, Table, Typography } from '@douyinfe/semi-ui';
|
||||
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 Link from 'next/link';
|
||||
import Router from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
const { Column } = Table;
|
||||
|
||||
const Page = () => {
|
||||
const { data: organization } = usePeronalOrganization();
|
||||
const {
|
||||
data: userOrganizations,
|
||||
loading: userOrganizationsLoading,
|
||||
error: userOrganizationsError,
|
||||
} = useUserOrganizations();
|
||||
|
||||
const gotoCreate = useCallback(() => {
|
||||
Router.push({
|
||||
pathname: '/app/org/create',
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (userOrganizations && userOrganizations.length) return;
|
||||
if (!organization) return;
|
||||
|
||||
Router.replace({
|
||||
pathname: `/app/org/[organizationId]`,
|
||||
query: { organizationId: organization.id },
|
||||
});
|
||||
}, [organization]);
|
||||
}, [organization, userOrganizations]);
|
||||
|
||||
return (
|
||||
<SingleColumnLayout>
|
||||
<div className="container">
|
||||
<div
|
||||
style={{
|
||||
padding: '10vh',
|
||||
textAlign: 'center',
|
||||
<div style={{ marginBottom: 24, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Title heading={3} style={{ margin: '8px 0' }}>
|
||||
组织列表
|
||||
</Title>
|
||||
<Button theme="solid" onClick={gotoCreate}>
|
||||
新建组织
|
||||
</Button>
|
||||
</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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Spin></Spin>
|
||||
</div>
|
||||
<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>
|
||||
</SingleColumnLayout>
|
||||
);
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
.wikiItemWrap {
|
||||
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 {
|
||||
.wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
|
|
@ -1,35 +1,51 @@
|
|||
import { Button, Typography } from '@douyinfe/semi-ui';
|
||||
import { Seo } from 'components/seo';
|
||||
import { toLogin, useUser } from 'data/user';
|
||||
import { SingleColumnLayout } from 'layouts/single-column';
|
||||
import type { NextPage } from 'next';
|
||||
import Router from 'next/router';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
const { Title } = Typography;
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
const Page: NextPage = () => {
|
||||
const gotoApp = useCallback(() => {
|
||||
const { user } = useUser();
|
||||
|
||||
const start = useCallback(() => {
|
||||
if (user) {
|
||||
Router.push(`/app`);
|
||||
} else {
|
||||
toLogin();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const toGithub = useCallback(() => {
|
||||
window.open('https://github.com/fantasticit/think');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SingleColumnLayout>
|
||||
<Seo title="主页" />
|
||||
<div className="container">
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Title heading={3} style={{ margin: '8px 0' }}>
|
||||
主页
|
||||
</Title>
|
||||
<div className={styles.wrap}>
|
||||
<div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Title style={{ margin: 24 }}>云策文档</Title>
|
||||
<Paragraph type="tertiary">
|
||||
云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: '10vh',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Button theme="solid" onClick={gotoApp}>
|
||||
前往组织空间
|
||||
<div style={{ margin: '48px 0', textAlign: 'center' }}>
|
||||
<Button theme="solid" onClick={start}>
|
||||
开始使用
|
||||
</Button>
|
||||
<Button style={{ marginLeft: 12 }} onClick={toGithub}>
|
||||
Github
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SingleColumnLayout>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Button, Col, Form, Layout, Modal, Row, Space, Toast, Typography } from
|
|||
import { Author } from 'components/author';
|
||||
import { LogoImage, LogoText } from 'components/logo';
|
||||
import { Seo } from 'components/seo';
|
||||
import { useRegister, useVerifyCode } from 'data/user';
|
||||
import { useRegister, useSystemPublicConfig, useVerifyCode } from 'data/user';
|
||||
import { isEmail } from 'helpers/validator';
|
||||
import { useInterval } from 'hooks/use-interval';
|
||||
import { useRouterQuery } from 'hooks/use-router-query';
|
||||
|
@ -24,6 +24,7 @@ const Page = () => {
|
|||
const [hasSendVerifyCode, toggleHasSendVerifyCode] = useToggle(false);
|
||||
const [countDown, setCountDown] = useState(0);
|
||||
const { register, loading } = useRegister();
|
||||
const { data: systemConfig } = useSystemPublicConfig();
|
||||
const { sendVerifyCode, loading: sendVerifyCodeLoading } = useVerifyCode();
|
||||
|
||||
const onFormChange = useCallback((formState) => {
|
||||
|
@ -133,6 +134,7 @@ const Page = () => {
|
|||
]}
|
||||
/>
|
||||
|
||||
{systemConfig && systemConfig.enableEmailVerify ? (
|
||||
<Row gutter={8} style={{ paddingTop: 12 }}>
|
||||
<Col span={16}>
|
||||
<Form.Input
|
||||
|
@ -144,11 +146,17 @@ const Page = () => {
|
|||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Button disabled={!email || countDown > 0} loading={sendVerifyCodeLoading} onClick={getVerifyCode} block>
|
||||
<Button
|
||||
disabled={!email || countDown > 0}
|
||||
loading={sendVerifyCodeLoading}
|
||||
onClick={getVerifyCode}
|
||||
block
|
||||
>
|
||||
{hasSendVerifyCode ? countDown : '获取验证码'}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
|
||||
<Button htmlType="submit" type="primary" theme="solid" block loading={loading} style={{ margin: '16px 0' }}>
|
||||
注册
|
||||
|
|
|
@ -10,7 +10,7 @@ interface AxiosInstance extends Axios {
|
|||
|
||||
export const HttpClient = axios.create({
|
||||
baseURL: process.env.SERVER_API_URL,
|
||||
timeout: 1 * 3 * 1000,
|
||||
timeout: process.env.NODE_ENV === 'production' ? 10 * 60 * 1000 : 3000,
|
||||
withCredentials: true,
|
||||
}) as AxiosInstance;
|
||||
|
||||
|
|
|
@ -7,3 +7,4 @@ export * from './template';
|
|||
export * from './comment';
|
||||
export * from './star';
|
||||
export * from './organization';
|
||||
export * from './system';
|
||||
|
|
|
@ -19,3 +19,4 @@ __exportStar(require("./template"), exports);
|
|||
__exportStar(require("./comment"), exports);
|
||||
__exportStar(require("./star"), exports);
|
||||
__exportStar(require("./organization"), exports);
|
||||
__exportStar(require("./system"), exports);
|
||||
|
|
|
@ -40,6 +40,14 @@ export declare const OrganizationApiDefinition: {
|
|||
server: "/update/:id";
|
||||
client: (id: IOrganization['id']) => string;
|
||||
};
|
||||
/**
|
||||
* 更新组织基本信息
|
||||
*/
|
||||
deleteOrganization: {
|
||||
method: "delete";
|
||||
server: "/delete/:id";
|
||||
client: (id: IOrganization['id']) => string;
|
||||
};
|
||||
/**
|
||||
* 获取组织成员
|
||||
*/
|
||||
|
|
|
@ -42,6 +42,14 @@ exports.OrganizationApiDefinition = {
|
|||
server: '/update/:id',
|
||||
client: function (id) { return "/organization/update/".concat(id); }
|
||||
},
|
||||
/**
|
||||
* 更新组织基本信息
|
||||
*/
|
||||
deleteOrganization: {
|
||||
method: 'delete',
|
||||
server: '/delete/:id',
|
||||
client: function (id) { return "/organization/delete/".concat(id); }
|
||||
},
|
||||
/**
|
||||
* 获取组织成员
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export declare const SystemApiDefinition: {
|
||||
getPublicConfig: {
|
||||
method: "get";
|
||||
server: "/";
|
||||
client: () => string;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
"use strict";
|
||||
exports.__esModule = true;
|
||||
exports.SystemApiDefinition = void 0;
|
||||
exports.SystemApiDefinition = {
|
||||
getPublicConfig: {
|
||||
method: 'get',
|
||||
server: '/',
|
||||
client: function () { return '/system'; }
|
||||
}
|
||||
};
|
|
@ -1,33 +1,2 @@
|
|||
"use strict";
|
||||
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;
|
||||
// }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export interface ISystemConfig {
|
||||
isSystemLocked: boolean;
|
||||
enableEmailVerify: boolean;
|
||||
emailServiceHost: string;
|
||||
emailServicePassword: string;
|
||||
emailServicePort: string;
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* 用户角色枚举
|
||||
*/
|
||||
export declare enum UserRole {
|
||||
normal = "normal",
|
||||
admin = "admin",
|
||||
superadmin = "superadmin"
|
||||
}
|
||||
/**
|
||||
* 用户状态枚举
|
||||
*/
|
||||
|
@ -22,7 +14,6 @@ export interface IUser {
|
|||
password?: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
role: UserRole;
|
||||
status: UserStatus;
|
||||
isSystemAdmin?: boolean;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
"use strict";
|
||||
exports.__esModule = true;
|
||||
exports.UserStatus = exports.UserRole = void 0;
|
||||
/**
|
||||
* 用户角色枚举
|
||||
*/
|
||||
var UserRole;
|
||||
(function (UserRole) {
|
||||
UserRole["normal"] = "normal";
|
||||
UserRole["admin"] = "admin";
|
||||
UserRole["superadmin"] = "superadmin";
|
||||
})(UserRole = exports.UserRole || (exports.UserRole = {}));
|
||||
exports.UserStatus = void 0;
|
||||
/**
|
||||
* 用户状态枚举
|
||||
*/
|
||||
|
|
|
@ -8,21 +8,6 @@ export declare enum WikiStatus {
|
|||
private = "private",
|
||||
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;
|
||||
updatedAt: Date;
|
||||
}
|
||||
/**
|
||||
* 知识库成员数据定义
|
||||
*/
|
||||
export interface IWikiUser extends IUser {
|
||||
userRole: WikiUserRole;
|
||||
userStatus: WikiUserStatus;
|
||||
isCreator: boolean;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
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["public"] = "public";
|
||||
})(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 = {}));
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
export declare const isPublicWiki: (currentStatus: IWiki['status']) => boolean;
|
||||
/**
|
||||
* 获取知识库成员角色对应文本
|
||||
* @param role 实例数据的 role 字段
|
||||
* @returns
|
||||
*/
|
||||
export declare const getWikiUserRoleText: (role: WikiUserRole) => string;
|
||||
/**
|
||||
* 检查文档是否公开
|
||||
* @param currentStatus document 实例数据的 status 字段
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
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");
|
||||
/**
|
||||
* 知识库状态列表数据
|
||||
|
@ -57,15 +57,6 @@ exports.getWikiStatusText = getWikiStatusText;
|
|||
*/
|
||||
var isPublicWiki = function (currentStatus) { return currentStatus === models_1.WikiStatus.public; };
|
||||
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 字段
|
||||
|
|
|
@ -7,3 +7,4 @@ export * from './template';
|
|||
export * from './comment';
|
||||
export * from './star';
|
||||
export * from './organization';
|
||||
export * from './system';
|
||||
|
|
|
@ -46,6 +46,15 @@ export const OrganizationApiDefinition = {
|
|||
client: (id: IOrganization['id']) => `/organization/update/${id}`,
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新组织基本信息
|
||||
*/
|
||||
deleteOrganization: {
|
||||
method: 'delete' as const,
|
||||
server: '/delete/:id' as const,
|
||||
client: (id: IOrganization['id']) => `/organization/delete/${id}`,
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取组织成员
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const SystemApiDefinition = {
|
||||
getPublicConfig: {
|
||||
method: 'get' as const,
|
||||
server: '/' as const,
|
||||
client: () => '/system',
|
||||
},
|
||||
};
|
|
@ -1,5 +1,3 @@
|
|||
import { IUser } from '../models';
|
||||
|
||||
export const UserApiDefinition = {
|
||||
/**
|
||||
* 获取用户
|
||||
|
|
|
@ -11,39 +11,3 @@ export interface IOrganization {
|
|||
createUserId: IUser['id'];
|
||||
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;
|
||||
// }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export interface ISystemConfig {
|
||||
isSystemLocked: boolean;
|
||||
enableEmailVerify: boolean;
|
||||
emailServiceHost: string;
|
||||
emailServicePassword: string;
|
||||
emailServicePort: string;
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
/**
|
||||
* 用户角色枚举
|
||||
*/
|
||||
export enum UserRole {
|
||||
normal = 'normal',
|
||||
admin = 'admin',
|
||||
superadmin = 'superadmin',
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户状态枚举
|
||||
*/
|
||||
|
@ -24,7 +15,6 @@ export interface IUser {
|
|||
password?: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
role: UserRole;
|
||||
status: UserStatus;
|
||||
isSystemAdmin?: boolean;
|
||||
}
|
||||
|
|
|
@ -10,23 +10,6 @@ export enum WikiStatus {
|
|||
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;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 知识库成员数据定义
|
||||
*/
|
||||
export interface IWikiUser extends IUser {
|
||||
userRole: WikiUserRole;
|
||||
userStatus: WikiUserStatus;
|
||||
isCreator: boolean;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 获取知识库成员角色对应文本
|
||||
* @param role 实例数据的 role 字段
|
||||
* @returns
|
||||
*/
|
||||
export const getWikiUserRoleText = (role: WikiUserRole) => {
|
||||
return WIKI_USER_ROLES.find((d) => d.value === role).label;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查文档是否公开
|
||||
* @param currentStatus document 实例数据的 status 字段
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
import { AuthEntity } from '@entities/auth.entity';
|
||||
import { CommentEntity } from '@entities/comment.entity';
|
||||
import { DocumentEntity } from '@entities/document.entity';
|
||||
// import { DocumentUserEntity } from '@entities/document-user.entity';
|
||||
import { MessageEntity } from '@entities/message.entity';
|
||||
import { OrganizationEntity } from '@entities/organization.entity';
|
||||
// import { OrganizationUserEntity } from '@entities/organization-user.entity';
|
||||
import { StarEntity } from '@entities/star.entity';
|
||||
import { SystemEntity } from '@entities/system.entity';
|
||||
import { TemplateEntity } from '@entities/template.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 { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||
import { IS_PRODUCTION } from '@helpers/env.helper';
|
||||
import { getLogFileName, ONE_DAY } from '@helpers/log.helper';
|
||||
import { AuthModule } from '@modules/auth.module';
|
||||
|
@ -42,17 +37,12 @@ const ENTITIES = [
|
|||
UserEntity,
|
||||
AuthEntity,
|
||||
OrganizationEntity,
|
||||
// OrganizationUserEntity,
|
||||
WikiEntity,
|
||||
// WikiUserEntity,
|
||||
DocumentEntity,
|
||||
// DocumentUserEntity,
|
||||
StarEntity,
|
||||
CommentEntity,
|
||||
MessageEntity,
|
||||
TemplateEntity,
|
||||
ViewEntity,
|
||||
VerifyEntity,
|
||||
SystemEntity,
|
||||
];
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export enum RedisDBEnum {
|
||||
documentVersion = 0,
|
||||
view = 1,
|
||||
verify = 2,
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { OperateUserAuthDto } from '@dtos/auth.dto';
|
||||
import { CreateDocumentDto } from '@dtos/create-document.dto';
|
||||
import { DocAuthDto } from '@dtos/doc-auth.dto';
|
||||
import { ShareDocumentDto } from '@dtos/share-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 { JwtGuard } from '@guard/jwt.guard';
|
||||
import {
|
||||
|
@ -23,10 +21,9 @@ import {
|
|||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
import { DocumentApiDefinition, DocumentStatus } from '@think/domains';
|
||||
import { DocumentApiDefinition, DocumentStatus, IPagination } from '@think/domains';
|
||||
|
||||
@Controller('document')
|
||||
// @UseGuards(DocumentAuthorityGuard)
|
||||
@UseGuards(DocumentStatusGuard)
|
||||
export class DocumentController {
|
||||
constructor(private readonly documentService: DocumentService) {}
|
||||
|
@ -78,7 +75,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(DocumentApiDefinition.getDetailById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getDocumentDetail(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.getDocumentDetail(req.user, documentId);
|
||||
|
@ -94,7 +90,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Patch(DocumentApiDefinition.updateById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('editable')
|
||||
@UseGuards(JwtGuard)
|
||||
async updateDocument(@Request() req, @Param('id') documentId, @Body() dto: UpdateDocumentDto) {
|
||||
return await this.documentService.updateDocument(req.user, documentId, dto);
|
||||
|
@ -109,7 +104,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(DocumentApiDefinition.getVersionById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getDocumentVersion(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.getDocumentVersion(req.user, documentId);
|
||||
|
@ -124,10 +118,9 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(DocumentApiDefinition.getMemberById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getDocUsers(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.getDocUsers(req.user, documentId);
|
||||
async getDocUsers(@Request() req, @Param('id') documentId, @Query() pagination: IPagination) {
|
||||
return await this.documentService.getDocUsers(req.user, documentId, pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,7 +133,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post(DocumentApiDefinition.addMemberById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async addDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) {
|
||||
return await this.documentService.addDocUser(req.user, documentId, dto);
|
||||
|
@ -156,7 +148,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Patch(DocumentApiDefinition.updateMemberById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async updateDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) {
|
||||
return await this.documentService.updateDocUser(req.user, documentId, dto);
|
||||
|
@ -172,7 +163,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post(DocumentApiDefinition.deleteMemberById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async deleteDocUser(@Request() req, @Param('id') documentId, @Body() dto: OperateUserAuthDto) {
|
||||
return await this.documentService.deleteDocUser(req.user, documentId, dto);
|
||||
|
@ -187,7 +177,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post(DocumentApiDefinition.getChildren.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getChildrenDocuments(@Request() req, @Body() data) {
|
||||
return await this.documentService.getChildrenDocuments(req.user, data);
|
||||
|
@ -202,7 +191,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Delete(DocumentApiDefinition.deleteById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async deleteDocument(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.deleteDocument(req.user, documentId);
|
||||
|
@ -218,7 +206,6 @@ export class DocumentController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post(DocumentApiDefinition.shareById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckDocumentAuthority('editable')
|
||||
@UseGuards(JwtGuard)
|
||||
async shareDocument(@Request() req, @Param('id') documentId, @Body() dto: ShareDocumentDto) {
|
||||
return await this.documentService.shareDocument(req.user, documentId, dto);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { OperateUserAuthDto } from '@dtos/auth.dto';
|
||||
import { CreateOrganizationDto } from '@dtos/organization.dto';
|
||||
// import { OrganizationUserDto } from '@dtos/organization-user.dto';
|
||||
import { JwtGuard } from '@guard/jwt.guard';
|
||||
import {
|
||||
Body,
|
||||
|
@ -13,12 +12,13 @@ import {
|
|||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
Request,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { OrganizationService } from '@services/organization.service';
|
||||
import { OrganizationApiDefinition } from '@think/domains';
|
||||
import { IPagination, OrganizationApiDefinition } from '@think/domains';
|
||||
|
||||
@Controller('organization')
|
||||
export class OrganizationController {
|
||||
|
@ -91,6 +91,20 @@ export class OrganizationController {
|
|||
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
|
||||
|
@ -100,8 +114,8 @@ export class OrganizationController {
|
|||
@Get(OrganizationApiDefinition.getMembers.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(JwtGuard)
|
||||
async getMembers(@Request() req, @Param('id') id) {
|
||||
return await this.organizationService.getMembers(req.user, id);
|
||||
async getMembers(@Request() req, @Param('id') id, @Query() pagination: IPagination) {
|
||||
return await this.organizationService.getMembers(req.user, id, pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 { SystemApiDefinition } from '@think/domains';
|
||||
|
||||
@Controller('system')
|
||||
export class SystemController {
|
||||
constructor(private readonly systemService: SystemService) {}
|
||||
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(SystemApiDefinition.getPublicConfig.server)
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async getPublicConfig() {
|
||||
return await this.systemService.getPublicConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { ShareWikiDto } from '@dtos/share-wiki.dto';
|
|||
import { UpdateWikiDto } from '@dtos/update-wiki.dto';
|
||||
import { JwtGuard } from '@guard/jwt.guard';
|
||||
import { CheckWikiStatus, WikiStatusGuard } from '@guard/wiki-status.guard';
|
||||
// import { CheckWikiUserRole, WikiUserRoleGuard } from '@guard/wiki-user.guard';
|
||||
import {
|
||||
Body,
|
||||
ClassSerializerInterceptor,
|
||||
|
@ -22,7 +21,7 @@ import {
|
|||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { WikiService } from '@services/wiki.service';
|
||||
import { IPagination, WikiApiDefinition, WikiStatus, WikiUserRole } from '@think/domains';
|
||||
import { IPagination, WikiApiDefinition, WikiStatus } from '@think/domains';
|
||||
|
||||
@Controller('wiki')
|
||||
export class WikiController {
|
||||
|
@ -93,8 +92,6 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(WikiApiDefinition.getHomeDocumentById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole()
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiHomeDocument(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiHomeDocument(req.user, wikiId);
|
||||
|
@ -109,8 +106,6 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(WikiApiDefinition.getTocsById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole()
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiTocs(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiTocs(req.user, wikiId);
|
||||
|
@ -126,29 +121,11 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Patch(WikiApiDefinition.updateTocsById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole()
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async orderWikiTocs(@Body() 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
|
||||
|
@ -158,8 +135,6 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(WikiApiDefinition.getDetailById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole()
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiDetail(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiDetail(req.user, wikiId);
|
||||
|
@ -167,7 +142,6 @@ export class WikiController {
|
|||
|
||||
/**
|
||||
* 修改知识库
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
|
@ -176,8 +150,6 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Patch(WikiApiDefinition.updateById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole(WikiUserRole.admin)
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async updateWiki(@Request() req, @Param('id') wikiId, @Body() dto: UpdateWikiDto) {
|
||||
return await this.wikiService.updateWiki(req.user, wikiId, dto);
|
||||
|
@ -185,7 +157,6 @@ export class WikiController {
|
|||
|
||||
/**
|
||||
* 删除知识库
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
|
@ -193,8 +164,6 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Delete(WikiApiDefinition.deleteById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole(WikiUserRole.admin)
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async deleteWiki(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.deleteWiki(req.user, wikiId);
|
||||
|
@ -202,7 +171,6 @@ export class WikiController {
|
|||
|
||||
/**
|
||||
* 查看知识库成员
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
|
@ -210,16 +178,13 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get(WikiApiDefinition.getMemberById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole(WikiUserRole.admin)
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiUsers(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiUsers(req.user, wikiId);
|
||||
async getWikiUsers(@Request() req, @Param('id') wikiId, @Query() pagination: IPagination) {
|
||||
return await this.wikiService.getWikiUsers(req.user, wikiId, pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加知识库成员
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
|
@ -228,8 +193,6 @@ export class WikiController {
|
|||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post(WikiApiDefinition.addMemberById.server)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
// @CheckWikiUserRole(WikiUserRole.admin)
|
||||
// @UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(JwtGuard)
|
||||
async addWikiUser(@Request() req, @Param('id') wikiId, @Body() dto: OperateUserAuthDto) {
|
||||
return await this.wikiService.addWikiUser(req.user, wikiId, dto);
|
||||
|
@ -237,7 +200,6 @@ export class WikiController {
|
|||
|
||||
/**
|
||||
* 更新知识库成员(一般为角色操作)
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
|
@ -253,7 +215,6 @@ export class WikiController {
|
|||
|
||||
/**
|
||||
* 删除知识库成员
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
|
@ -269,7 +230,6 @@ export class WikiController {
|
|||
|
||||
/**
|
||||
* 分享(或关闭分享)知识库
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
|
|
|
@ -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个字符' })
|
||||
@IsString({ message: '邮箱验证码错误(正确类型为:String)' })
|
||||
@IsNotEmpty({ message: '邮箱验证码不能为空' })
|
||||
verifyCode: string;
|
||||
@IsOptional({ message: '邮箱验证码不能为空' })
|
||||
verifyCode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { IsEmail, IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
|
||||
import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
|
||||
|
||||
export class LoginUserDto {
|
||||
@IsString({ message: '用户名称类型错误(正确类型为:String)' })
|
||||
|
|
|
@ -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;
|
||||
// }
|
|
@ -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;
|
||||
// }
|
|
@ -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;
|
||||
}
|
|
@ -8,10 +8,15 @@ export class SystemEntity {
|
|||
/**
|
||||
* 是否锁定系统,锁定后除系统管理员外均不可登录,同时禁止注册
|
||||
*/
|
||||
|
||||
@Column({ type: 'boolean', default: false, comment: '是否锁定系统' })
|
||||
isSystemLocked: boolean;
|
||||
|
||||
/**
|
||||
* 启用邮箱校验后,必须通过邮箱验证码验证后注册
|
||||
*/
|
||||
@Column({ type: 'boolean', default: false, comment: '是否启用邮箱校验' })
|
||||
enableEmailVerify: boolean;
|
||||
|
||||
/**
|
||||
* 邮箱服务地址
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UserRole, UserStatus } from '@think/domains';
|
||||
import { UserStatus } from '@think/domains';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { Exclude } from 'class-transformer';
|
||||
import { BeforeInsert, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
@ -37,14 +37,6 @@ export class UserEntity {
|
|||
@Column({ type: 'boolean', default: false, comment: '是否为系统管理员' })
|
||||
isSystemAdmin: boolean;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: UserRole,
|
||||
default: UserRole.normal,
|
||||
comment: '用户角色',
|
||||
})
|
||||
public role: UserRole;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: UserStatus,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { CommentController } from '@controllers/comment.controller';
|
||||
import { CommentEntity } from '@entities/comment.entity';
|
||||
import { AuthModule } from '@modules/auth.module';
|
||||
import { DocumentModule } from '@modules/document.module';
|
||||
import { MessageModule } from '@modules/message.module';
|
||||
import { UserModule } from '@modules/user.module';
|
||||
|
@ -11,6 +12,7 @@ import { CommentService } from '@services/comment.service';
|
|||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([CommentEntity]),
|
||||
forwardRef(() => AuthModule),
|
||||
forwardRef(() => UserModule),
|
||||
forwardRef(() => WikiModule),
|
||||
forwardRef(() => DocumentModule),
|
||||
|
|
|
@ -3,6 +3,7 @@ import { OrganizationEntity } from '@entities/organization.entity';
|
|||
import { AuthModule } from '@modules/auth.module';
|
||||
import { MessageModule } from '@modules/message.module';
|
||||
import { UserModule } from '@modules/user.module';
|
||||
import { WikiModule } from '@modules/wiki.module';
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { OrganizationService } from '@services/organization.service';
|
||||
|
@ -13,6 +14,7 @@ import { OrganizationService } from '@services/organization.service';
|
|||
forwardRef(() => UserModule),
|
||||
forwardRef(() => MessageModule),
|
||||
forwardRef(() => AuthModule),
|
||||
forwardRef(() => WikiModule),
|
||||
],
|
||||
providers: [OrganizationService],
|
||||
exports: [OrganizationService],
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { VerifyController } from '@controllers/verify.controller';
|
||||
import { VerifyEntity } from '@entities/verify.entity';
|
||||
import { SystemModule } from '@modules/system.module';
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { VerifyService } from '@services/verify.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([VerifyEntity]), forwardRef(() => SystemModule)],
|
||||
imports: [forwardRef(() => SystemModule)],
|
||||
providers: [VerifyService],
|
||||
exports: [VerifyService],
|
||||
controllers: [VerifyController],
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import { ViewController } from '@controllers/view.controller';
|
||||
import { ViewEntity } from '@entities/view.entity';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ViewService } from '@services/view.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([ViewEntity])],
|
||||
providers: [ViewService],
|
||||
exports: [ViewService],
|
||||
controllers: [ViewController],
|
||||
|
|
|
@ -3,7 +3,7 @@ import { AuthEntity } from '@entities/auth.entity';
|
|||
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
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 { Repository } from 'typeorm';
|
||||
|
||||
|
@ -41,8 +41,6 @@ export class AuthService {
|
|||
const wrappedAuth = { userId, ...auth };
|
||||
const oldAuth = await this.authRepo.findOne(wrappedAuth);
|
||||
|
||||
// TODO: 这里可以判断权限继承
|
||||
|
||||
let newAuth: AuthEntity;
|
||||
|
||||
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
|
||||
|
@ -97,7 +137,7 @@ export class AuthService {
|
|||
const userAuth = await this.authRepo.findOne(conditions);
|
||||
|
||||
if (!userAuth || userAuth.auth === AuthEnum.noAccess) {
|
||||
throw new HttpException('您没有权限查看', HttpStatus.FORBIDDEN);
|
||||
throw new HttpException('您没有权限', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return userAuth;
|
||||
|
@ -120,7 +160,7 @@ export class AuthService {
|
|||
const userAuth = await this.authRepo.findOne(conditions);
|
||||
|
||||
if (!userAuth || ![AuthEnum.creator, AuthEnum.admin].includes(userAuth.auth)) {
|
||||
throw new HttpException('您没有权限编辑', HttpStatus.FORBIDDEN);
|
||||
throw new HttpException('您没有权限', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return userAuth;
|
||||
|
@ -143,7 +183,7 @@ export class AuthService {
|
|||
const userAuth = await this.authRepo.findOne(conditions);
|
||||
|
||||
if (!userAuth || ![AuthEnum.creator].includes(userAuth.auth)) {
|
||||
throw new HttpException('您没有权限删除', HttpStatus.FORBIDDEN);
|
||||
throw new HttpException('您没有权限', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return userAuth;
|
||||
|
@ -156,6 +196,12 @@ export class AuthService {
|
|||
* @param dto
|
||||
*/
|
||||
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> = {
|
||||
organizationId: dto.organizationId,
|
||||
wikiId: dto.wikiId || null,
|
||||
|
@ -168,17 +214,17 @@ export class AuthService {
|
|||
});
|
||||
|
||||
if (!currentUserAuth) {
|
||||
throw new HttpException('您没有权限操作1', HttpStatus.FORBIDDEN);
|
||||
throw new HttpException('您没有权限操作', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
// 仅创建者、管理员可操作他人权限
|
||||
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) {
|
||||
throw new HttpException('您没有权限操作3', HttpStatus.FORBIDDEN);
|
||||
throw new HttpException('您没有权限操作', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const maybeTargetUserAuth = await this.authRepo.findOne({
|
||||
|
@ -189,12 +235,12 @@ export class AuthService {
|
|||
if (maybeTargetUserAuth) {
|
||||
// 对方是创建者,无权操作
|
||||
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) {
|
||||
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']) {
|
||||
const [data, total] = await this.authRepo
|
||||
async getUsersAuthInOrganization(organizationId: IOrganization['id'], pagination: IPagination | null) {
|
||||
const query = await this.authRepo
|
||||
.createQueryBuilder('auth')
|
||||
.where('auth.auth IN (:...types)', {
|
||||
types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess],
|
||||
|
@ -311,18 +359,28 @@ export class AuthService {
|
|||
.andWhere('auth.organizationId=:organizationId')
|
||||
.andWhere('auth.wikiId is NULL')
|
||||
.andWhere('auth.documentId is NULL')
|
||||
.setParameter('organizationId', organizationId)
|
||||
.getManyAndCount();
|
||||
.setParameter('organizationId', organizationId);
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定知识库的所有用户权限
|
||||
* @param userId
|
||||
* @param organizationId
|
||||
* @param wikiId
|
||||
* @param pagination 分页参数,不传获取所有
|
||||
* @returns
|
||||
*/
|
||||
async getUsersAuthInWiki(organizationId: IOrganization['id'], wikiId: IWiki['id']) {
|
||||
const [data, total] = await this.authRepo
|
||||
async getUsersAuthInWiki(organizationId: IOrganization['id'], wikiId: IWiki['id'], pagination: IPagination | null) {
|
||||
const query = await this.authRepo
|
||||
.createQueryBuilder('auth')
|
||||
.where('auth.auth IN (:...types)', {
|
||||
types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess],
|
||||
|
@ -331,8 +389,14 @@ export class AuthService {
|
|||
.andWhere('auth.wikiId=:wikiId')
|
||||
.andWhere('auth.documentId is NULL')
|
||||
.setParameter('organizationId', organizationId)
|
||||
.setParameter('wikiId', wikiId)
|
||||
.getManyAndCount();
|
||||
.setParameter('wikiId', wikiId);
|
||||
|
||||
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 };
|
||||
}
|
||||
|
@ -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']) {
|
||||
const [data, total] = await this.authRepo
|
||||
async getUsersAuthInDocument(
|
||||
organizationId: IOrganization['id'],
|
||||
wikiId: IWiki['id'],
|
||||
documentId: IDocument['id'],
|
||||
pagination: IPagination | null
|
||||
) {
|
||||
const query = await this.authRepo
|
||||
.createQueryBuilder('auth')
|
||||
.where('auth.auth IN (:...types)', {
|
||||
types: [AuthEnum.creator, AuthEnum.admin, AuthEnum.member, AuthEnum.noAccess],
|
||||
|
@ -427,8 +500,14 @@ export class AuthService {
|
|||
.andWhere('auth.documentId=:documentId')
|
||||
.setParameter('organizationId', organizationId)
|
||||
.setParameter('wikiId', wikiId)
|
||||
.setParameter('documentId', documentId)
|
||||
.getManyAndCount();
|
||||
.setParameter('documentId', documentId);
|
||||
|
||||
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 };
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import { ConfigService } from '@nestjs/config';
|
|||
import { DocumentService } from '@services/document.service';
|
||||
import { DocumentVersionService } from '@services/document-version.service';
|
||||
import { TemplateService } from '@services/template.service';
|
||||
import { OutUser, UserService } from '@services/user.service';
|
||||
import { DocumentStatus } from '@think/domains';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DocumentStatus, IUser } from '@think/domains';
|
||||
import * as lodash from 'lodash';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
|
@ -101,14 +101,16 @@ export class CollaborationService {
|
|||
}
|
||||
return { user: { name: '匿名用户' } };
|
||||
} else {
|
||||
// TODO:权限校验
|
||||
// const authority = await this.documentService.getDocumentAuthority(targetId, user.id);
|
||||
// if (!authority.readable) {
|
||||
// throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
|
||||
// }
|
||||
// if (!authority.editable) {
|
||||
// connection.readOnly = true;
|
||||
// }
|
||||
const authority = await this.documentService.getDocumentUserAuth(user.id, targetId);
|
||||
|
||||
if (!authority.readable) {
|
||||
throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
if (!authority.editable) {
|
||||
connection.readOnly = true;
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
|
@ -121,9 +123,11 @@ export class CollaborationService {
|
|||
}
|
||||
|
||||
const template = await this.templateService.findById(targetId);
|
||||
|
||||
if (template.createUserId !== user.id) {
|
||||
throw new HttpException('您无权查看此模板', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
|
@ -149,6 +153,9 @@ export class CollaborationService {
|
|||
switch (docType) {
|
||||
case 'document': {
|
||||
const res = await this.documentService.findById(targetId);
|
||||
if (!res) {
|
||||
throw new Error('文档不存在');
|
||||
}
|
||||
state = res.state;
|
||||
break;
|
||||
}
|
||||
|
@ -179,7 +186,7 @@ export class CollaborationService {
|
|||
const docType = requestParameters.get('docType');
|
||||
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);
|
||||
this.debounce(
|
||||
`onStoreDocumentVersion-${documentId}`,
|
||||
|
@ -218,7 +225,7 @@ export class CollaborationService {
|
|||
const node = TiptapTransformer.fromYdoc(data.document);
|
||||
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));
|
||||
await updateHandler({ id: userId } as OutUser, targetId, {
|
||||
await updateHandler({ id: userId } as IUser, targetId, {
|
||||
title,
|
||||
content: JSON.stringify(node),
|
||||
state,
|
||||
|
@ -234,7 +241,7 @@ export class CollaborationService {
|
|||
if (docType === 'document') {
|
||||
const data = await this.documentService.findById(targetId);
|
||||
if (data && !data.title) {
|
||||
await this.documentService.updateDocument({ id: userId } as OutUser, targetId, {
|
||||
await this.documentService.updateDocument({ id: userId } as IUser, targetId, {
|
||||
title: '未命名文档',
|
||||
});
|
||||
}
|
||||
|
@ -244,7 +251,7 @@ export class CollaborationService {
|
|||
if (docType === 'template') {
|
||||
const data = await this.templateService.findById(targetId);
|
||||
if (data && !data.title) {
|
||||
await this.templateService.updateTemplate({ id: userId } as OutUser, targetId, {
|
||||
await this.templateService.updateTemplate({ id: userId } as IUser, targetId, {
|
||||
title: '未命名模板',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ import { CommentEntity } from '@entities/comment.entity';
|
|||
import { parseUserAgent } from '@helpers/ua.helper';
|
||||
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AuthService } from '@services/auth.service';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
import { MessageService } from '@services/message.service';
|
||||
import { OutUser, UserService } from '@services/user.service';
|
||||
import { buildMessageURL, DocumentStatus } from '@think/domains';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { AuthEnum, buildMessageURL, DocumentStatus, IUser } from '@think/domains';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
|
@ -14,9 +15,17 @@ export class CommentService {
|
|||
constructor(
|
||||
@InjectRepository(CommentEntity)
|
||||
private readonly commentRepo: Repository<CommentEntity>,
|
||||
|
||||
@Inject(forwardRef(() => AuthService))
|
||||
private readonly authService: AuthService,
|
||||
|
||||
@Inject(forwardRef(() => UserService))
|
||||
private readonly userService: UserService,
|
||||
|
||||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService,
|
||||
private readonly userService: UserService,
|
||||
|
||||
@Inject(forwardRef(() => DocumentService))
|
||||
private readonly documentService: DocumentService
|
||||
) {}
|
||||
|
||||
|
@ -44,20 +53,20 @@ export class CommentService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async create(user: OutUser, userAgent: string, dto: CommentDto) {
|
||||
async create(user: IUser, userAgent: string, dto: CommentDto) {
|
||||
const { documentId, html, replyUserId } = dto;
|
||||
|
||||
const doc = await this.documentService.findById(documentId);
|
||||
|
||||
if (doc.status !== DocumentStatus.public) {
|
||||
// TODO:权限校验
|
||||
// const docAuth = await this.documentService.getDocumentAuthority(documentId, user.id);
|
||||
// if (!docAuth) {
|
||||
// throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
// }
|
||||
// if (!docAuth.readable) {
|
||||
// throw new HttpException('权限不足,无法评论', HttpStatus.FORBIDDEN);
|
||||
// }
|
||||
const authority = await this.documentService.getDocumentUserAuth(user.id, documentId);
|
||||
|
||||
if (!authority) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
if (!authority.readable) {
|
||||
throw new HttpException('权限不足,无法评论', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
const { text: uaText } = parseUserAgent(userAgent);
|
||||
|
@ -74,21 +83,23 @@ export class CommentService {
|
|||
const res = await this.commentRepo.create(comment);
|
||||
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(
|
||||
// wikiUsersAuth.map(async (userAuth) => {
|
||||
// await this.messageService.notify(userAuth.user, {
|
||||
// title: `文档「${doc.title}」收到新评论`,
|
||||
// message: `文档「${doc.title}」收到新评论,快去看看!`,
|
||||
// url: buildMessageURL('toDocument')({
|
||||
// organizationId: doc.organizationId,
|
||||
// wikiId: doc.wikiId,
|
||||
// documentId: doc.id,
|
||||
// }),
|
||||
// });
|
||||
// })
|
||||
// );
|
||||
await Promise.all(
|
||||
users
|
||||
.filter((user) => user.auth !== AuthEnum.noAccess)
|
||||
.map((user) => {
|
||||
this.messageService.notify(user.userId, {
|
||||
title: `文档「${doc.title}」收到新评论`,
|
||||
message: `文档「${doc.title}」收到新评论,快去看看!`,
|
||||
url: buildMessageURL('toDocument')({
|
||||
organizationId: doc.organizationId,
|
||||
wikiId: doc.wikiId,
|
||||
documentId: doc.id,
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -180,21 +191,24 @@ export class CommentService {
|
|||
const newData = await this.commentRepo.merge(old, { html: dto.html });
|
||||
|
||||
const doc = await this.documentService.findById(old.documentId);
|
||||
// const wikiUsersAuth = await this.documentService.getDocUsersWithoutAuthCheck(user, old.documentId);
|
||||
|
||||
// await Promise.all(
|
||||
// wikiUsersAuth.map(async (userAuth) => {
|
||||
// await this.messageService.notify(userAuth.user, {
|
||||
// title: `文档「${doc.title}」评论更新`,
|
||||
// message: `文档「${doc.title}」的评论已更新,快去看看!`,
|
||||
// url: buildMessageURL('toDocument')({
|
||||
// organizationId: doc.organizationId,
|
||||
// wikiId: doc.wikiId,
|
||||
// documentId: doc.id,
|
||||
// }),
|
||||
// });
|
||||
// })
|
||||
// );
|
||||
const { data: users } = await this.authService.getUsersAuthInDocument(doc.organizationId, doc.wikiId, doc.id, null);
|
||||
|
||||
await Promise.all(
|
||||
users
|
||||
.filter((user) => user.auth !== AuthEnum.noAccess)
|
||||
.map((user) => {
|
||||
this.messageService.notify(user.userId, {
|
||||
title: `文档「${doc.title}」收到新评论`,
|
||||
message: `文档「${doc.title}」收到新评论,快去看看!`,
|
||||
url: buildMessageURL('toDocument')({
|
||||
organizationId: doc.organizationId,
|
||||
wikiId: doc.wikiId,
|
||||
documentId: doc.id,
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return this.commentRepo.save(newData);
|
||||
}
|
||||
|
@ -205,20 +219,25 @@ export class CommentService {
|
|||
throw new HttpException('您不是评论创建者,无法删除', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
const doc = await this.documentService.findById(data.documentId);
|
||||
// const wikiUsersAuth = await this.documentService.getDocUsersWithoutAuthCheck(user, data.documentId);
|
||||
// await Promise.all(
|
||||
// wikiUsersAuth.map(async (userAuth) => {
|
||||
// await this.messageService.notify(userAuth.user, {
|
||||
// title: `文档「${doc.title}」的评论已被删除`,
|
||||
// message: `文档「${doc.title}」的评论已被删除,快去看看`,
|
||||
// url: buildMessageURL('toDocument')({
|
||||
// organizationId: doc.organizationId,
|
||||
// wikiId: doc.wikiId,
|
||||
// documentId: doc.id,
|
||||
// }),
|
||||
// });
|
||||
// })
|
||||
// );
|
||||
|
||||
const { data: users } = await this.authService.getUsersAuthInDocument(doc.organizationId, doc.wikiId, doc.id, null);
|
||||
|
||||
await Promise.all(
|
||||
users
|
||||
.filter((user) => user.auth !== AuthEnum.noAccess)
|
||||
.map((user) => {
|
||||
this.messageService.notify(user.userId, {
|
||||
title: `文档「${doc.title}」收到新评论`,
|
||||
message: `文档「${doc.title}」收到新评论,快去看看!`,
|
||||
url: buildMessageURL('toDocument')({
|
||||
organizationId: doc.organizationId,
|
||||
wikiId: doc.wikiId,
|
||||
documentId: doc.id,
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return this.commentRepo.remove(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { OperateUserAuthDto } from '@dtos/auth.dto';
|
||||
import { CreateDocumentDto } from '@dtos/create-document.dto';
|
||||
// import { DocAuthDto } from '@dtos/doc-auth.dto';
|
||||
import { ShareDocumentDto } from '@dtos/share-document.dto';
|
||||
import { UpdateDocumentDto } from '@dtos/update-document.dto';
|
||||
import { DocumentEntity } from '@entities/document.entity';
|
||||
|
@ -12,11 +11,11 @@ import { CollaborationService } from '@services/collaboration.service';
|
|||
import { DocumentVersionService } from '@services/document-version.service';
|
||||
import { MessageService } from '@services/message.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 { WikiService } from '@services/wiki.service';
|
||||
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 * as lodash from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
|
@ -27,9 +26,6 @@ export class DocumentService {
|
|||
private documentVersionService: DocumentVersionService;
|
||||
|
||||
constructor(
|
||||
// @InjectRepository(DocumentUserEntity)
|
||||
// public readonly documentUserRepo: Repository<DocumentUserEntity>,
|
||||
|
||||
@InjectRepository(DocumentEntity)
|
||||
public readonly documentRepo: Repository<DocumentEntity>,
|
||||
|
||||
|
@ -70,7 +66,7 @@ export class DocumentService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
public async findById(id: string) {
|
||||
public async findById(id: string): Promise<Partial<DocumentEntity>> {
|
||||
const document = await this.documentRepo.findOne(id);
|
||||
return instanceToPlain(document);
|
||||
}
|
||||
|
@ -81,7 +77,7 @@ export class DocumentService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
public async findByIds(ids: string[]) {
|
||||
public async findByIds(ids: string[]): Promise<Array<Partial<DocumentEntity>>> {
|
||||
const documents = await this.documentRepo.findByIds(ids);
|
||||
return documents.map((doc) => instanceToPlain(doc));
|
||||
}
|
||||
|
@ -95,90 +91,6 @@ export class DocumentService {
|
|||
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
|
||||
|
@ -186,7 +98,7 @@ export class DocumentService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async addDocUser(user: OutUser, documentId, dto: OperateUserAuthDto) {
|
||||
async addDocUser(user: IUser, documentId, dto: OperateUserAuthDto) {
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
|
@ -199,12 +111,32 @@ export class DocumentService {
|
|||
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, {
|
||||
auth: dto.userAuth,
|
||||
organizationId: doc.organizationId,
|
||||
wikiId: doc.wikiId,
|
||||
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
|
||||
* @returns
|
||||
*/
|
||||
async updateDocUser(user: OutUser, documentId, dto: OperateUserAuthDto) {
|
||||
async updateDocUser(user: IUser, documentId, dto: OperateUserAuthDto) {
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
|
@ -233,6 +165,16 @@ export class DocumentService {
|
|||
wikiId: doc.wikiId,
|
||||
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
|
||||
* @returns
|
||||
*/
|
||||
async deleteDocUser(user: OutUser, documentId, dto: OperateUserAuthDto) {
|
||||
async deleteDocUser(user: IUser, documentId, dto: OperateUserAuthDto) {
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
|
@ -261,6 +203,16 @@ export class DocumentService {
|
|||
wikiId: doc.wikiId,
|
||||
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 wikiId
|
||||
*/
|
||||
async getDocUsers(user: OutUser, documentId) {
|
||||
async getDocUsers(user: IUser, documentId, pagination) {
|
||||
const doc = await this.documentRepo.findOne({ id: documentId });
|
||||
|
||||
if (!doc) {
|
||||
|
@ -284,7 +236,8 @@ export class DocumentService {
|
|||
const { data: auths, total } = await this.authService.getUsersAuthInDocument(
|
||||
doc.organizationId,
|
||||
doc.wikiId,
|
||||
doc.id
|
||||
doc.id,
|
||||
pagination
|
||||
);
|
||||
|
||||
const res = await Promise.all(
|
||||
|
@ -297,29 +250,6 @@ export class DocumentService {
|
|||
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
|
||||
|
@ -327,7 +257,7 @@ export class DocumentService {
|
|||
* @param isWikiHome 知识库首页文档
|
||||
* @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, {
|
||||
organizationId: dto.organizationId,
|
||||
wikiId: dto.wikiId,
|
||||
|
@ -375,7 +305,11 @@ export class DocumentService {
|
|||
}
|
||||
|
||||
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([
|
||||
...userAuths
|
||||
|
@ -388,7 +322,7 @@ export class DocumentService {
|
|||
documentId: document.id,
|
||||
});
|
||||
}),
|
||||
await this.authService.createOrUpdateAuth(user.id, {
|
||||
this.authService.createOrUpdateAuth(user.id, {
|
||||
auth: AuthEnum.creator,
|
||||
organizationId: document.organizationId,
|
||||
wikiId: document.wikiId,
|
||||
|
@ -399,26 +333,27 @@ export class DocumentService {
|
|||
return instanceToPlain(document);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 删除知识库下所有文档
|
||||
// * @param user
|
||||
// * @param wikiId
|
||||
// */
|
||||
// async deleteWikiDocuments(user, wikiId) {
|
||||
// const docs = await this.documentRepo.find({ wikiId });
|
||||
// await Promise.all(
|
||||
// docs.map((doc) => {
|
||||
// return this.deleteDocument(user, doc.id);
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
/**
|
||||
* 删除知识库下所有文档
|
||||
* @param user
|
||||
* @param wikiId
|
||||
*/
|
||||
async deleteWikiDocuments(user, wikiId) {
|
||||
const docs = await this.documentRepo.find({ wikiId });
|
||||
await Promise.all(
|
||||
docs.map((doc) => {
|
||||
return this.deleteDocument(user, doc.id);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文档
|
||||
* @param idd
|
||||
*/
|
||||
async deleteDocument(user: OutUser, documentId) {
|
||||
async deleteDocument(user: IUser, documentId) {
|
||||
const document = await this.documentRepo.findOne(documentId);
|
||||
|
||||
if (document.isWikiHome) {
|
||||
const isWikiExist = await this.wikiService.findById(document.wikiId);
|
||||
if (isWikiExist) {
|
||||
|
@ -435,6 +370,7 @@ export class DocumentService {
|
|||
const children = await this.documentRepo.find({
|
||||
parentDocumentId: document.id,
|
||||
});
|
||||
|
||||
if (children && children.length) {
|
||||
const parentDocumentId = document.parentDocumentId;
|
||||
await Promise.all(
|
||||
|
@ -448,11 +384,10 @@ export class DocumentService {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO:权限删除
|
||||
// const auths = await this.documentUserRepo.find({ documentId });
|
||||
// await this.documentUserRepo.remove(auths);
|
||||
|
||||
return this.documentRepo.remove(document);
|
||||
await Promise.all([
|
||||
this.authService.deleteDocument(document.organizationId, document.wikiId, document.id),
|
||||
this.documentRepo.remove(document),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -462,7 +397,7 @@ export class DocumentService {
|
|||
* @param dto
|
||||
* @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);
|
||||
|
||||
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 documentId
|
||||
* @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);
|
||||
return data;
|
||||
}
|
||||
|
@ -526,7 +488,7 @@ export class DocumentService {
|
|||
* 分享(或关闭分享)文档
|
||||
* @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);
|
||||
await this.authService.canEdit(user.id, {
|
||||
organizationId: document.organizationId,
|
||||
|
@ -570,7 +532,6 @@ export class DocumentService {
|
|||
]);
|
||||
// 异步创建
|
||||
this.viewService.create(null, document);
|
||||
|
||||
return { ...doc, views, wiki, createUser };
|
||||
}
|
||||
|
||||
|
@ -581,7 +542,7 @@ export class DocumentService {
|
|||
* @returns
|
||||
*/
|
||||
public async getChildrenDocuments(
|
||||
user: OutUser,
|
||||
user: IUser,
|
||||
data: {
|
||||
wikiId: string;
|
||||
documentId?: string;
|
||||
|
@ -694,7 +655,7 @@ export class DocumentService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
public async getRecentDocuments(user: OutUser, organizationId) {
|
||||
public async getRecentDocuments(user: IUser, organizationId) {
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId: organizationId,
|
||||
wikiId: null,
|
||||
|
@ -744,24 +705,25 @@ export class DocumentService {
|
|||
.createQueryBuilder('document')
|
||||
.andWhere('document.organizationId = :organizationId')
|
||||
.andWhere('document.title LIKE :keyword')
|
||||
// FIXME: 编辑器内容的 json 字段可能也被匹配
|
||||
.orWhere('document.content LIKE :keyword')
|
||||
.setParameter('organizationId', organizationId)
|
||||
.setParameter('keyword', `%${keyword}%`)
|
||||
.getMany();
|
||||
|
||||
// const ret = await Promise.all(
|
||||
// res.map(async (doc) => {
|
||||
// const auth = await this.documentUserRepo.findOne({
|
||||
// documentId: doc.id,
|
||||
// userId: user.id,
|
||||
// });
|
||||
// return auth && auth.readable ? doc : null;
|
||||
// })
|
||||
// );
|
||||
const ret = await Promise.all(
|
||||
res.map(async (doc) => {
|
||||
const auth = await this.authService.getAuth(user.Id, {
|
||||
organizationId: doc.organizationId,
|
||||
wikiId: doc.wikiId,
|
||||
documentId: doc.id,
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { MessageEntity } from '@entities/message.entity';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { OutUser } from '@services/user.service';
|
||||
import { IUser } from '@think/domains';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
|
@ -17,8 +17,8 @@ export class MessageService {
|
|||
* @param msg
|
||||
* @returns
|
||||
*/
|
||||
async notify(user: OutUser, msg) {
|
||||
const data = { userId: user.id, ...msg };
|
||||
async notify(userId: IUser['id'], msg) {
|
||||
const data = { userId, ...msg };
|
||||
const res = await this.messageRepo.create(data);
|
||||
const ret = await this.messageRepo.save(res);
|
||||
return ret;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { OperateUserAuthDto } from '@dtos/auth.dto';
|
||||
import { CreateOrganizationDto } from '@dtos/organization.dto';
|
||||
// import { OperateUserAuthDto } from '@dtos/organization-user.dto';
|
||||
import { OrganizationEntity } from '@entities/organization.entity';
|
||||
import { UserEntity } from '@entities/user.entity';
|
||||
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 { MessageService } from '@services/message.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { WikiService } from '@services/wiki.service';
|
||||
import { AuthEnum, buildMessageURL, IOrganization, IUser } from '@think/domains';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
|
@ -24,7 +24,10 @@ export class OrganizationService {
|
|||
private readonly userService: UserService,
|
||||
|
||||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService
|
||||
private readonly messageService: MessageService,
|
||||
|
||||
@Inject(forwardRef(() => WikiService))
|
||||
private readonly wikiService: WikiService
|
||||
) {}
|
||||
|
||||
public async findById(id: string) {
|
||||
|
@ -90,6 +93,27 @@ export class OrganizationService {
|
|||
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
|
||||
|
@ -101,7 +125,7 @@ export class OrganizationService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取用户除个人组织外可访问的组织
|
||||
* 获取用户可访问的组织
|
||||
* @param user
|
||||
*/
|
||||
public async getUserOrganizations(user: IUser) {
|
||||
|
@ -130,7 +154,7 @@ export class OrganizationService {
|
|||
* @param shortId
|
||||
* @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 });
|
||||
|
||||
if (!organization) {
|
||||
|
@ -143,7 +167,7 @@ export class OrganizationService {
|
|||
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 users = await this.userService.findByIds(userIds);
|
||||
|
@ -185,7 +209,7 @@ export class OrganizationService {
|
|||
documentId: null,
|
||||
});
|
||||
|
||||
await this.messageService.notify(targetUser, {
|
||||
await this.messageService.notify(targetUser.id, {
|
||||
title: `您被添加到组织「${organization.name}」`,
|
||||
message: `您被添加到知识库「${organization.name}」,快去看看吧!`,
|
||||
url: buildMessageURL('toOrganization')({
|
||||
|
@ -223,7 +247,7 @@ export class OrganizationService {
|
|||
documentId: null,
|
||||
});
|
||||
|
||||
await this.messageService.notify(targetUser, {
|
||||
await this.messageService.notify(targetUser.id, {
|
||||
title: `组织「${organization.name}」权限变更`,
|
||||
message: `您在组织「${organization.name}」权限已变更,快去看看吧!`,
|
||||
url: buildMessageURL('toOrganization')({
|
||||
|
@ -261,9 +285,9 @@ export class OrganizationService {
|
|||
documentId: null,
|
||||
});
|
||||
|
||||
await this.messageService.notify(targetUser, {
|
||||
await this.messageService.notify(targetUser.id, {
|
||||
title: `组织「${organization.name}」权限收回`,
|
||||
message: `您在组织「${organization.name}」权限已收回,快去看看吧!`,
|
||||
message: `您在组织「${organization.name}」权限已收回!`,
|
||||
url: buildMessageURL('toOrganization')({
|
||||
organizationId: organization.id,
|
||||
}),
|
||||
|
|
|
@ -5,9 +5,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||
import { AuthService } from '@services/auth.service';
|
||||
import { DocumentService } from '@services/document.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 { IDocument } from '@think/domains';
|
||||
import { IDocument, IUser } from '@think/domains';
|
||||
import * as lodash from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
|
@ -39,7 +39,7 @@ export class StarService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async toggleStar(user: OutUser, dto: StarDto) {
|
||||
async toggleStar(user: IUser, dto: StarDto) {
|
||||
const data = {
|
||||
...dto,
|
||||
userId: user.id,
|
||||
|
@ -61,7 +61,7 @@ export class StarService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async isStared(user: OutUser, dto: StarDto) {
|
||||
async isStared(user: IUser, dto: StarDto) {
|
||||
const res = await this.starRepo.findOne({ userId: user.id, ...dto });
|
||||
return Boolean(res);
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ export class StarService {
|
|||
* @param user
|
||||
* @returns
|
||||
*/
|
||||
async getStarWikisInOrganization(user: OutUser, organizationId) {
|
||||
async getStarWikisInOrganization(user: IUser, organizationId) {
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId: organizationId,
|
||||
wikiId: null,
|
||||
|
@ -100,7 +100,7 @@ export class StarService {
|
|||
* @param user
|
||||
* @returns
|
||||
*/
|
||||
async getStarDocumentsInWiki(user: OutUser, dto: StarDto) {
|
||||
async getStarDocumentsInWiki(user: IUser, dto: StarDto) {
|
||||
const records = await this.starRepo.find({
|
||||
userId: user.id,
|
||||
wikiId: dto.wikiId,
|
||||
|
@ -114,7 +114,7 @@ export class StarService {
|
|||
const createUser = await this.userService.findById(doc.createUserId);
|
||||
return { createUser, ...doc };
|
||||
})
|
||||
)) as Array<IDocument & { createUser: OutUser }>;
|
||||
)) as Array<IDocument & { createUser: IUser }>;
|
||||
|
||||
return withCreateUserRes
|
||||
.map((document) => {
|
||||
|
@ -134,7 +134,7 @@ export class StarService {
|
|||
* @param user
|
||||
* @returns
|
||||
*/
|
||||
async getStarDocumentsInOrganization(user: OutUser, organizationId) {
|
||||
async getStarDocumentsInOrganization(user: IUser, organizationId) {
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId: organizationId,
|
||||
wikiId: null,
|
||||
|
|
|
@ -40,21 +40,18 @@ export class SystemService {
|
|||
*/
|
||||
private async loadFromConfigFile() {
|
||||
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');
|
||||
|
||||
let emailConfig = {};
|
||||
|
||||
if (emailConfigFromConfigFile && typeof emailConfigFromConfigFile === 'object') {
|
||||
emailConfig = {
|
||||
emailServiceHost: emailConfigFromConfigFile.host,
|
||||
emailServicePort: emailConfigFromConfigFile.port,
|
||||
emailServiceUser: emailConfigFromConfigFile.user,
|
||||
emailServicePassword: emailConfigFromConfigFile.password,
|
||||
emailServiceHost: currentConfig ? currentConfig.emailServiceHost : emailConfigFromConfigFile.host,
|
||||
emailServicePort: currentConfig ? currentConfig.emailServicePort : emailConfigFromConfigFile.port,
|
||||
emailServiceUser: currentConfig ? currentConfig.emailServiceUser : emailConfigFromConfigFile.user,
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ import { TemplateDto } from '@dtos/template.dto';
|
|||
import { TemplateEntity } from '@entities/template.entity';
|
||||
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
||||
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 * as lodash from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
|
@ -45,7 +46,7 @@ export class TemplateService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async create(user: OutUser, dto: TemplateDto) {
|
||||
async create(user: IUser, dto: TemplateDto) {
|
||||
const data = {
|
||||
createUserId: user.id,
|
||||
...dto,
|
||||
|
@ -103,7 +104,7 @@ export class TemplateService {
|
|||
* @param templateId
|
||||
* @returns
|
||||
*/
|
||||
async useTemplate(user: OutUser, templateId) {
|
||||
async useTemplate(user: IUser, templateId) {
|
||||
const data = await this.templateRepo.findOne(templateId);
|
||||
if (user.id !== data.createUserId && !data.isPublic) {
|
||||
throw new HttpException('您不是模板创建者,无法编辑', HttpStatus.FORBIDDEN);
|
||||
|
@ -146,7 +147,7 @@ export class TemplateService {
|
|||
* @param queryParams
|
||||
* @returns
|
||||
*/
|
||||
async getOwnTemplates(user: OutUser, queryParams) {
|
||||
async getOwnTemplates(user: IUser, queryParams) {
|
||||
const query = this.templateRepo
|
||||
.createQueryBuilder('template')
|
||||
.where('template.createUserId=:createUserId')
|
||||
|
|
|
@ -18,8 +18,6 @@ import { IUser, UserStatus } from '@think/domains';
|
|||
import { instanceToPlain } from 'class-transformer';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
export type OutUser = Omit<UserEntity, 'comparePassword' | 'encryptPassword' | 'encrypt' | 'password'>;
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
|
@ -95,9 +93,9 @@ export class UserService {
|
|||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
async findById(id): Promise<OutUser> {
|
||||
async findById(id): Promise<IUser> {
|
||||
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
|
||||
* @returns
|
||||
*/
|
||||
async findOne(opts: Partial<OutUser>): Promise<UserEntity> {
|
||||
async findOne(opts: Partial<UserEntity>): Promise<UserEntity> {
|
||||
const user = await this.userRepo.findOne(opts);
|
||||
return user;
|
||||
}
|
||||
|
@ -115,9 +113,9 @@ export class UserService {
|
|||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
async findByIds(ids): Promise<OutUser[]> {
|
||||
async findByIds(ids): Promise<IUser[]> {
|
||||
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
|
||||
* @returns
|
||||
*/
|
||||
async createUser(user: RegisterUserDto): Promise<OutUser> {
|
||||
async createUser(user: RegisterUserDto): Promise<IUser> {
|
||||
const currentSystemConfig = await this.systemService.getConfigFromDatabase();
|
||||
|
||||
if (currentSystemConfig.isSystemLocked) {
|
||||
|
@ -144,7 +142,10 @@ export class UserService {
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -158,20 +159,7 @@ export class UserService {
|
|||
isPersonal: true,
|
||||
});
|
||||
|
||||
// const wiki = await this.wikiService.createWiki(createdUser, {
|
||||
// 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;
|
||||
return instanceToPlain(createdUser) as IUser;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,7 +201,7 @@ export class UserService {
|
|||
* @param user
|
||||
* @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 { name, password } = user;
|
||||
|
@ -237,7 +225,7 @@ export class UserService {
|
|||
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 domain = this.confifgService.get('client.siteDomain');
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
@ -261,11 +249,11 @@ export class UserService {
|
|||
* @param dto
|
||||
* @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 res = await this.userRepo.merge(oldData, dto);
|
||||
const ret = await this.userRepo.save(res);
|
||||
return instanceToPlain(ret) as OutUser;
|
||||
return instanceToPlain(ret) as IUser;
|
||||
}
|
||||
|
||||
async decodeToken(token) {
|
||||
|
|
|
@ -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 { InjectRepository } from '@nestjs/typeorm';
|
||||
import { SystemService } from '@services/system.service';
|
||||
import Redis from 'ioredis';
|
||||
import { randomInt } from 'node:crypto';
|
||||
import { Repository } from 'typeorm';
|
||||
import { isEmail } from 'validator';
|
||||
|
||||
@Injectable()
|
||||
export class VerifyService {
|
||||
constructor(
|
||||
@InjectRepository(VerifyEntity)
|
||||
private readonly verifyRepo: Repository<VerifyEntity>,
|
||||
private redis: Redis;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => SystemService))
|
||||
private readonly systemService: SystemService
|
||||
) {}
|
||||
) {
|
||||
this.buildRedis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除验证记录
|
||||
* @param record
|
||||
*/
|
||||
private async deleteVerifyCode(id) {
|
||||
const record = await this.verifyRepo.findOne(id);
|
||||
await this.verifyRepo.remove(record);
|
||||
private async buildRedis() {
|
||||
try {
|
||||
this.redis = await buildRedis(RedisDBEnum.verify);
|
||||
console.log('[think] 验证码服务启动成功');
|
||||
} catch (e) {
|
||||
console.error(`[think] 验证码服务启动错误: "${e.message}"`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定邮箱发送验证码
|
||||
* @param email
|
||||
*/
|
||||
public async sendVerifyCode(email: string) {
|
||||
public sendVerifyCode = async (email: string) => {
|
||||
if (!email || !isEmail(email)) {
|
||||
throw new HttpException('请检查邮箱地址', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
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({
|
||||
to: email,
|
||||
subject: '验证码',
|
||||
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
|
||||
* @returns
|
||||
*/
|
||||
public async checkVerifyCode(email: string, verifyCode: string) {
|
||||
public checkVerifyCode = async (email: string, verifyCode: string) => {
|
||||
if (!email || !isEmail(email)) {
|
||||
throw new HttpException('请检查邮箱地址', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const ret = await this.verifyRepo.findOne({ email, verifyCode });
|
||||
const ret = await this.redis.get(`verify-${email}`);
|
||||
|
||||
if (!ret) {
|
||||
throw new HttpException('验证码已过期,请重新获取', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (ret !== verifyCode) {
|
||||
throw new HttpException('验证码错误', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return Boolean(ret);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
import { RedisDBEnum } from '@constants/*';
|
||||
import { DocumentEntity } from '@entities/document.entity';
|
||||
import { UserEntity } from '@entities/user.entity';
|
||||
import { ViewEntity } from '@entities/view.entity';
|
||||
import { buildRedis } from '@helpers/redis.helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { IDocument, IOrganization, IUser } from '@think/domains';
|
||||
import Redis from 'ioredis';
|
||||
import * as lodash from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class ViewService {
|
||||
private redis: Redis;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepo: Repository<ViewEntity>
|
||||
) {
|
||||
constructor() {
|
||||
this.buildRedis();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,21 +2,17 @@ import { OperateUserAuthDto } from '@dtos/auth.dto';
|
|||
import { CreateWikiDto } from '@dtos/create-wiki.dto';
|
||||
import { ShareWikiDto } from '@dtos/share-wiki.dto';
|
||||
import { UpdateWikiDto } from '@dtos/update-wiki.dto';
|
||||
// import { WikiUserDto } from '@dtos/wiki-user.dto';
|
||||
import { WikiEntity } from '@entities/wiki.entity';
|
||||
// import { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||
import { array2tree } from '@helpers/tree.helper';
|
||||
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AuthService } from '@services/auth.service';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
import { MessageService } from '@services/message.service';
|
||||
import { OrganizationService } from '@services/organization.service';
|
||||
import { StarService } from '@services/star.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { OutUser } from '@services/user.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 * as lodash from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
|
@ -30,9 +26,6 @@ export class WikiService {
|
|||
@Inject(forwardRef(() => AuthService))
|
||||
private readonly authService: AuthService,
|
||||
|
||||
// @InjectRepository(WikiUserEntity)
|
||||
// private readonly wikiUserRepo: Repository<WikiUserEntity>,
|
||||
|
||||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService,
|
||||
|
||||
|
@ -46,10 +39,7 @@ export class WikiService {
|
|||
private readonly userService: UserService,
|
||||
|
||||
@Inject(forwardRef(() => ViewService))
|
||||
private readonly viewService: ViewService,
|
||||
|
||||
@Inject(forwardRef(() => OrganizationService))
|
||||
private readonly organizationService: OrganizationService
|
||||
private readonly viewService: ViewService
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
@ -73,115 +63,6 @@ export class WikiService {
|
|||
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
|
||||
|
@ -189,7 +70,7 @@ export class WikiService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async addWikiUser(user: OutUser, wikiId, dto: OperateUserAuthDto) {
|
||||
async addWikiUser(user: IUser, wikiId, dto: OperateUserAuthDto) {
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
|
@ -197,27 +78,40 @@ export class WikiService {
|
|||
}
|
||||
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
const homeDoc = await this.getWikiHomeDocument(user, wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
if (
|
||||
!(await this.authService.getAuth(targetUser.id, {
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: null,
|
||||
documentId: null,
|
||||
}))
|
||||
) {
|
||||
throw new HttpException('该用户非组织成员', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const homeDoc = await this.getWikiHomeDocument(user, wikiId);
|
||||
|
||||
await Promise.all([
|
||||
this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
auth: dto.userAuth,
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
documentId: null,
|
||||
});
|
||||
}),
|
||||
|
||||
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
auth: dto.userAuth,
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
documentId: homeDoc.id,
|
||||
});
|
||||
}),
|
||||
]);
|
||||
|
||||
await this.messageService.notify(targetUser, {
|
||||
await this.messageService.notify(targetUser.id, {
|
||||
title: `您被添加到知识库「${wiki.name}」`,
|
||||
message: `您被添加到知识库「${wiki.name}」,快去看看吧!`,
|
||||
url: buildMessageURL('toWiki')({
|
||||
|
@ -234,7 +128,7 @@ export class WikiService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async updateWikiUser(user: OutUser, wikiId, dto: OperateUserAuthDto) {
|
||||
async updateWikiUser(user: IUser, wikiId, dto: OperateUserAuthDto) {
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
|
@ -248,21 +142,23 @@ export class WikiService {
|
|||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
await Promise.all([
|
||||
this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
auth: dto.userAuth,
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
documentId: null,
|
||||
});
|
||||
}),
|
||||
|
||||
await this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
this.authService.createOrUpdateOtherUserAuth(user.id, targetUser.id, {
|
||||
auth: dto.userAuth,
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
documentId: homeDoc.id,
|
||||
});
|
||||
}),
|
||||
]);
|
||||
|
||||
await this.messageService.notify(targetUser, {
|
||||
await this.messageService.notify(targetUser.id, {
|
||||
title: `您在知识库「${wiki.name}」的权限有更新`,
|
||||
message: `您在知识库「${wiki.name}」的权限有更新,快去看看吧!`,
|
||||
url: buildMessageURL('toWiki')({
|
||||
|
@ -279,7 +175,7 @@ export class WikiService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async deleteWikiUser(user: OutUser, wikiId, dto: OperateUserAuthDto) {
|
||||
async deleteWikiUser(user: IUser, wikiId, dto: OperateUserAuthDto) {
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
|
@ -293,23 +189,25 @@ export class WikiService {
|
|||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.authService.deleteOtherUserAuth(user.id, targetUser.id, {
|
||||
await Promise.all([
|
||||
this.authService.deleteOtherUserAuth(user.id, targetUser.id, {
|
||||
auth: dto.userAuth,
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
documentId: null,
|
||||
});
|
||||
}),
|
||||
|
||||
await this.authService.deleteOtherUserAuth(user.id, targetUser.id, {
|
||||
this.authService.deleteOtherUserAuth(user.id, targetUser.id, {
|
||||
auth: dto.userAuth,
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
documentId: homeDoc.id,
|
||||
});
|
||||
}),
|
||||
]);
|
||||
|
||||
await this.messageService.notify(targetUser, {
|
||||
title: `您在「${wiki.name}」的权限已变更`,
|
||||
message: `您在「${wiki.name}」的权限已变更,快去看看吧!`,
|
||||
await this.messageService.notify(targetUser.id, {
|
||||
title: `知识库「${wiki.name}」的权限收回`,
|
||||
message: `您在知识库「「${wiki.name}」的权限已收回!`,
|
||||
url: buildMessageURL('toWiki')({
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
|
@ -322,7 +220,7 @@ export class WikiService {
|
|||
* @param userId
|
||||
* @param wikiId
|
||||
*/
|
||||
async getWikiUsers(user, wikiId) {
|
||||
async getWikiUsers(user, wikiId, pagination) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
|
@ -335,7 +233,11 @@ export class WikiService {
|
|||
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 users = await this.userService.findByIds(userIds);
|
||||
|
@ -350,28 +252,13 @@ export class WikiService {
|
|||
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 dto CreateWikiDto
|
||||
* @returns
|
||||
*/
|
||||
async createWiki(user: OutUser, dto: CreateWikiDto) {
|
||||
async createWiki(user: IUser, dto: CreateWikiDto) {
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId: dto.organizationId,
|
||||
wikiId: null,
|
||||
|
@ -386,7 +273,7 @@ export class WikiService {
|
|||
const toSaveWiki = await this.wikiRepo.create(data);
|
||||
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([
|
||||
...userAuths
|
||||
|
@ -399,6 +286,7 @@ export class WikiService {
|
|||
documentId: null,
|
||||
});
|
||||
}),
|
||||
|
||||
await this.authService.createOrUpdateAuth(user.id, {
|
||||
auth: AuthEnum.creator,
|
||||
organizationId: wiki.organizationId,
|
||||
|
@ -426,7 +314,6 @@ export class WikiService {
|
|||
const homeDocumentId = homeDoc.id;
|
||||
const withHomeDocumentIdWiki = await this.wikiRepo.merge(wiki, { homeDocumentId });
|
||||
await this.wikiRepo.save(withHomeDocumentIdWiki);
|
||||
|
||||
return withHomeDocumentIdWiki;
|
||||
}
|
||||
|
||||
|
@ -436,7 +323,7 @@ export class WikiService {
|
|||
* @param pagination
|
||||
* @returns
|
||||
*/
|
||||
async getAllWikis(user: OutUser, organizationId, pagination: IPagination) {
|
||||
async getAllWikis(user: IUser, organizationId, pagination: IPagination) {
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId,
|
||||
wikiId: null,
|
||||
|
@ -467,7 +354,7 @@ export class WikiService {
|
|||
* @param pagination
|
||||
* @returns
|
||||
*/
|
||||
async getOwnWikis(user: OutUser, organizationId, pagination: IPagination) {
|
||||
async getOwnWikis(user: IUser, organizationId, pagination: IPagination) {
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId,
|
||||
wikiId: null,
|
||||
|
@ -497,7 +384,7 @@ export class WikiService {
|
|||
* @param pagination
|
||||
* @returns
|
||||
*/
|
||||
async getJoinWikis(user: OutUser, organizationId, pagination: IPagination) {
|
||||
async getJoinWikis(user: IUser, organizationId, pagination: IPagination) {
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId,
|
||||
wikiId: null,
|
||||
|
@ -528,7 +415,7 @@ export class WikiService {
|
|||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getWikiDetail(user: OutUser, wikiId: string) {
|
||||
async getWikiDetail(user: IUser, wikiId: string) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId: wiki.organizationId,
|
||||
|
@ -545,7 +432,7 @@ export class WikiService {
|
|||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getWikiHomeDocument(user: OutUser, wikiId) {
|
||||
async getWikiHomeDocument(user: IUser, wikiId) {
|
||||
const res = await this.documentService.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
await this.authService.canView(user.id, {
|
||||
organizationId: res.organizationId,
|
||||
|
@ -562,7 +449,7 @@ export class WikiService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async updateWiki(user: OutUser, wikiId, dto: UpdateWikiDto) {
|
||||
async updateWiki(user: IUser, wikiId, dto: UpdateWikiDto) {
|
||||
const oldData = await this.wikiRepo.findOne(wikiId);
|
||||
if (!oldData) {
|
||||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
|
@ -586,26 +473,35 @@ export class WikiService {
|
|||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async deleteWiki(user: OutUser, wikiId) {
|
||||
async deleteWiki(user: IUser, wikiId) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
await this.authService.canDelete(user.id, {
|
||||
organizationId: wiki.organizationId,
|
||||
wikiId: wiki.id,
|
||||
documentId: null,
|
||||
});
|
||||
await this.wikiRepo.remove(wiki);
|
||||
// TODO: 删除相应文档以及对应权限
|
||||
// if (user.id !== wiki.createUserId) {
|
||||
// throw new HttpException('您不是创建者,无法删除该知识库', HttpStatus.FORBIDDEN);
|
||||
// }
|
||||
|
||||
// await this.documentService.deleteWikiDocuments(user, wikiId);
|
||||
|
||||
// const users = await this.wikiUserRepo.find({ wikiId });
|
||||
// await Promise.all([this.wikiUserRepo.remove(users)]);
|
||||
await Promise.all([
|
||||
this.authService.deleteWiki(wiki.organizationId, wiki.id),
|
||||
this.wikiRepo.remove(wiki),
|
||||
this.documentService.deleteWikiDocuments(user, wikiId),
|
||||
]);
|
||||
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
|
||||
|
@ -613,7 +509,7 @@ export class WikiService {
|
|||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
async shareWiki(user: OutUser, wikiId, dto: ShareWikiDto) {
|
||||
async shareWiki(user: IUser, wikiId, dto: ShareWikiDto) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
|
@ -668,7 +564,7 @@ export class WikiService {
|
|||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getWikiTocs(user: OutUser, wikiId) {
|
||||
async getWikiTocs(user: IUser, wikiId) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue