mirror of
https://github.com/haiwen/seahub.git
synced 2025-04-27 11:01:14 +00:00
Add excl draw module 4 (#7740)
* add exdraw apis * temporarily submit * rebase exdraw_apis * Interacting with the exdraw-server * update * optimize code * optimize code --------- Co-authored-by: ‘JoinTyang’ <yangtong1009@163.com> Co-authored-by: 小强 <shuntian@xiaoqiangdeMacBook-Pro.local>
This commit is contained in:
parent
444e690255
commit
8e2be63d3f
222
frontend/package-lock.json
generated
222
frontend/package-lock.json
generated
@ -55,6 +55,7 @@
|
||||
"react-select": "5.9.0",
|
||||
"react-transition-group": "4.4.5",
|
||||
"reactstrap": "9.2.3",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"svgo-loader": "^3.0.1",
|
||||
"unified": "^7.0.0",
|
||||
@ -5638,42 +5639,6 @@
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
|
||||
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/engine.io-client": {
|
||||
"version": "6.6.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1",
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/is-plain-obj": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
|
||||
@ -5686,32 +5651,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/socket.io-client": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.6.1",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/trough": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz",
|
||||
@ -5741,34 +5680,6 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sdoc-editor/node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/seafile-calendar": {
|
||||
"version": "0.0.28",
|
||||
"resolved": "https://registry.npmmirror.com/@seafile/seafile-calendar/-/seafile-calendar-0.0.28.tgz",
|
||||
@ -12237,6 +12148,71 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1",
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client/node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.0",
|
||||
"resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
|
||||
@ -27066,6 +27042,64 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.6.1",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/sockjs": {
|
||||
"version": "0.3.24",
|
||||
"resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz",
|
||||
@ -31257,6 +31291,14 @@
|
||||
"node": ">=0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
|
||||
|
@ -50,6 +50,7 @@
|
||||
"react-select": "5.9.0",
|
||||
"react-transition-group": "4.4.5",
|
||||
"reactstrap": "9.2.3",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"svgo-loader": "^3.0.1",
|
||||
"unified": "^7.0.0",
|
||||
|
@ -0,0 +1,28 @@
|
||||
import axios from 'axios';
|
||||
|
||||
class ExdrawServerApi {
|
||||
|
||||
constructor(options) {
|
||||
this.server = options.exdrawServer;
|
||||
this.docUuid = options.exdrawUuid;
|
||||
this.accessToken = options.accessToken;
|
||||
}
|
||||
|
||||
getSceneContent() {
|
||||
const { server, docUuid, accessToken } = this;
|
||||
const url = `${server}/api/v1/exdraw/${docUuid}/`;
|
||||
|
||||
return axios.get(url, { headers: { Authorization: `Token ${accessToken}` } });
|
||||
}
|
||||
|
||||
saveSceneContent(content) {
|
||||
const { server, docUuid, accessToken } = this;
|
||||
const url = `${server}/api/v1/exdraw/${docUuid}/`;
|
||||
const formData = new FormData();
|
||||
formData.append('doc_content', content);
|
||||
|
||||
return axios.post(url, formData, { headers: { Authorization: `Token ${accessToken}` } });
|
||||
}
|
||||
}
|
||||
|
||||
export default ExdrawServerApi;
|
@ -19,6 +19,12 @@ class EditorApi {
|
||||
return seafileAPI.getFileContent(downLoadUrl);
|
||||
});
|
||||
};
|
||||
|
||||
getExdrawToken = () => {
|
||||
return seafileAPI.getExdrawToken(repoID, filePath).then((res => {
|
||||
return res.data.access_token;
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
const editorApi = new EditorApi();
|
||||
|
@ -6,24 +6,36 @@ import { gettext } from '../../utils/constants';
|
||||
import toaster from '../../components/toast';
|
||||
import { SAVE_INTERVAL_TIME } from './constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import ExdrawServerApi from './collab/exdraw-server-api';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const { docUuid, excalidrawServerUrl } = window.app.pageOptions;
|
||||
|
||||
const ExcaliEditor = () => {
|
||||
const [fileContent, setFileContent] = useState(null);
|
||||
const editorRef = useRef(null);
|
||||
const isChangedRef = useRef(false);
|
||||
const [isFetching, setIsFetching] = useState(true);
|
||||
const exdrawServerConfigRef = useRef({
|
||||
exdrawServer: '',
|
||||
exdrawUuid: '',
|
||||
accessToken: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
editorApi.getFileContent().then(res => {
|
||||
if (res.data?.appState?.collaborators && !Array.isArray(res.data.appState.collaborators)) {
|
||||
// collaborators.forEach is not a function
|
||||
res.data['appState']['collaborators'] = [];
|
||||
}
|
||||
editorApi.getExdrawToken().then(res => {
|
||||
exdrawServerConfigRef.current = {
|
||||
exdrawServer: excalidrawServerUrl,
|
||||
exdrawUuid: docUuid,
|
||||
accessToken: res
|
||||
};
|
||||
const exdrawServerApi = new ExdrawServerApi(exdrawServerConfigRef.current);
|
||||
exdrawServerApi.getSceneContent().then(res => {
|
||||
setFileContent(res.data);
|
||||
setIsFetching(false);
|
||||
});
|
||||
});
|
||||
onSetFavicon();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@ -31,7 +43,8 @@ const ExcaliEditor = () => {
|
||||
const saveSceneContent = useCallback(async () => {
|
||||
if (isChangedRef.current) {
|
||||
try {
|
||||
await editorApi.saveContent(JSON.stringify(editorRef.current));
|
||||
const exdrawServerApi = new ExdrawServerApi(exdrawServerConfigRef.current);
|
||||
await exdrawServerApi.saveSceneContent(JSON.stringify(editorRef.current));
|
||||
isChangedRef.current = false;
|
||||
toaster.success(gettext('Successfully saved'), { duration: 2 });
|
||||
} catch {
|
||||
|
@ -21,7 +21,6 @@ const SimpleEditor = ({
|
||||
},
|
||||
tools: { image: false },
|
||||
};
|
||||
|
||||
const handleChange = () => {
|
||||
const elements = excalidrawAPI.getSceneElements();
|
||||
if (hasChanged(elements, prevElementsRef.current)) {
|
||||
@ -69,6 +68,22 @@ const SimpleEditor = ({
|
||||
onChange={handleChange}
|
||||
UIOptions={UIOptions}
|
||||
langCode={langList[window.app.config.lang] || 'en'}
|
||||
renderTopRightUI={() => {
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
background: '#70b1ec',
|
||||
border: 'none',
|
||||
color: '#fff',
|
||||
width: 'max-content',
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
onClick={() => onSaveContent(excalidrawAPI.getSceneElements())}
|
||||
>
|
||||
Save me
|
||||
</button>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<MainMenu>
|
||||
<MainMenu.DefaultItems.Export />
|
||||
|
@ -2222,6 +2222,21 @@ class SeafileAPI {
|
||||
return this._sendPostRequest(url, formData);
|
||||
}
|
||||
|
||||
sysAdminListFileTransferLogs(page, perPage) {
|
||||
const url = this.server + '/api/v2.1/admin/logs/repo-transfer-logs/';
|
||||
let params = {
|
||||
page: page,
|
||||
per_page: perPage
|
||||
};
|
||||
return this.req.get(url, { params: params });
|
||||
}
|
||||
|
||||
getExdrawToken(repoID, filePath) {
|
||||
const url = `/api/v2.1/exdraw/access-token/${repoID}/?p=${filePath}`;
|
||||
|
||||
return this.req.get(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let seafileAPI = new SeafileAPI();
|
||||
|
@ -37,6 +37,7 @@ from seahub.base.models import FileComment
|
||||
from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, OFFICE_TEMPLATE_ROOT
|
||||
from seahub.api2.endpoints.utils import convert_file, sdoc_convert_to_docx
|
||||
from seahub.seadoc.utils import get_seadoc_file_uuid
|
||||
from seahub.exdraw.utils import get_exdraw_file_uuid
|
||||
from seaserv import seafile_api
|
||||
from pysearpc import SearpcError
|
||||
|
||||
@ -266,6 +267,9 @@ class FileView(APIView):
|
||||
if new_file_name.endswith('.sdoc'):
|
||||
doc_uuid = get_seadoc_file_uuid(repo, new_file_path)
|
||||
file_info['doc_uuid'] = doc_uuid
|
||||
if new_file_name.endswith('.exdraw'):
|
||||
file_uuid = get_exdraw_file_uuid(repo, new_file_path)
|
||||
file_info['file_uuid'] = file_uuid
|
||||
return Response(file_info)
|
||||
|
||||
if operation == 'rename':
|
||||
|
0
seahub/exdraw/__init__.py
Normal file
0
seahub/exdraw/__init__.py
Normal file
200
seahub/exdraw/apis.py
Normal file
200
seahub/exdraw/apis.py
Normal file
@ -0,0 +1,200 @@
|
||||
import os
|
||||
import logging
|
||||
import requests
|
||||
import posixpath
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from seaserv import seafile_api
|
||||
|
||||
from seahub.views import check_folder_permission
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.exdraw.utils import is_valid_exdraw_access_token, get_exdraw_upload_link, get_exdraw_download_link, \
|
||||
get_exdraw_file_uuid, gen_exdraw_access_token
|
||||
|
||||
from seahub.utils.file_types import EXCALIDRAW
|
||||
from seahub.utils.file_op import if_locked_by_online_office
|
||||
from seahub.utils import get_file_type_and_ext, normalize_file_path, is_pro_version
|
||||
from seahub.tags.models import FileUUIDMap
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExdrawAccessToken(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle, )
|
||||
|
||||
def get(self, request, repo_id):
|
||||
username = request.user.username
|
||||
# argument check
|
||||
path = request.GET.get('p', None)
|
||||
if not path:
|
||||
error_msg = 'p invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
path = normalize_file_path(path)
|
||||
parent_dir = os.path.dirname(path)
|
||||
filename = os.path.basename(path)
|
||||
|
||||
filetype, fileext = get_file_type_and_ext(filename)
|
||||
if filetype != EXCALIDRAW:
|
||||
error_msg = 'exdraw file type %s invalid.' % filetype
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# resource check
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
try:
|
||||
obj_id = seafile_api.get_file_id_by_path(repo_id, path)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
if not obj_id:
|
||||
error_msg = 'File %s not found.' % path
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# permission check
|
||||
permission = check_folder_permission(request, repo_id, parent_dir)
|
||||
if not permission:
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
file_uuid = get_exdraw_file_uuid(repo, path)
|
||||
access_token = gen_exdraw_access_token(file_uuid, filename, username, permission=permission)
|
||||
|
||||
return Response({'access_token': access_token})
|
||||
|
||||
|
||||
class ExdrawUploadFile(APIView):
|
||||
authentication_classes = ()
|
||||
throttle_classes = (UserRateThrottle, )
|
||||
|
||||
def post(self, request, file_uuid):
|
||||
# jwt permission check
|
||||
auth = request.headers.get('authorization', '').split()
|
||||
if not is_valid_exdraw_access_token(auth, file_uuid):
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
file = request.FILES.get('file', None)
|
||||
if not file:
|
||||
error_msg = 'file not found.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
|
||||
if not uuid_map:
|
||||
error_msg = 'exdraw uuid %s not found.' % file_uuid
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
|
||||
if filetype != EXCALIDRAW:
|
||||
error_msg = 'exdraw file type %s invalid.' % filetype
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
|
||||
file_path = os.path.normpath(file_path)
|
||||
file_id = seafile_api.get_file_id_by_path(uuid_map.repo_id, file_path)
|
||||
if not file_id: # save file anyway
|
||||
seafile_api.post_empty_file(
|
||||
uuid_map.repo_id, uuid_map.parent_path, uuid_map.filename, '')
|
||||
|
||||
last_modify_user = request.POST.get('last_modify_user', '')
|
||||
upload_link = get_exdraw_upload_link(uuid_map, last_modify_user)
|
||||
if not upload_link:
|
||||
error_msg = 'exdraw file %s not found.' % uuid_map.filename
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# update file
|
||||
files = {'file': file}
|
||||
data = {'filename': uuid_map.filename, 'target_file': file_path}
|
||||
resp = requests.post(upload_link, files=files, data=data)
|
||||
if not resp.ok:
|
||||
logger.error('save exdraw failed %s, %s' % (file_uuid, resp.text))
|
||||
return api_error(resp.status_code, resp.content)
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
|
||||
class ExdrawDownloadLink(APIView):
|
||||
authentication_classes = ()
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request, file_uuid):
|
||||
# jwt permission check
|
||||
auth = request.headers.get('authorization', '').split()
|
||||
if not is_valid_exdraw_access_token(auth, file_uuid):
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
|
||||
if not uuid_map:
|
||||
error_msg = 'exdraw uuid %s not found.' % file_uuid
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
|
||||
if filetype != EXCALIDRAW:
|
||||
error_msg = 'exdraw file type %s invalid.' % filetype
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
download_link = get_exdraw_download_link(uuid_map)
|
||||
if not download_link:
|
||||
error_msg = 'exdraw file %s not found.' % uuid_map.filename
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
return Response({'download_link': download_link})
|
||||
|
||||
|
||||
class ExdrawEditorCallBack(APIView):
|
||||
authentication_classes = ()
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def post(self, request, file_uuid):
|
||||
# jwt permission check
|
||||
auth = request.headers.get('authorization', '').split()
|
||||
if not is_valid_exdraw_access_token(auth, file_uuid):
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
# file info check
|
||||
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
|
||||
if not uuid_map:
|
||||
error_msg = 'exdraw uuid %s not found.' % file_uuid
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
|
||||
if filetype != EXCALIDRAW:
|
||||
error_msg = 'exdraw file type %s invalid.' % filetype
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# currently only implement unlock file
|
||||
exdraw_status = request.POST.get('status', '')
|
||||
if exdraw_status != 'no_write':
|
||||
error_msg = 'status invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# unlock file
|
||||
repo_id = uuid_map.repo_id
|
||||
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
|
||||
file_path = os.path.normpath(file_path)
|
||||
try:
|
||||
if is_pro_version() and if_locked_by_online_office(repo_id, file_path):
|
||||
seafile_api.unlock_file(repo_id, file_path)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
10
seahub/exdraw/urls.py
Normal file
10
seahub/exdraw/urls.py
Normal file
@ -0,0 +1,10 @@
|
||||
from django.urls import re_path
|
||||
from .apis import ExdrawAccessToken, ExdrawDownloadLink, ExdrawUploadFile, ExdrawEditorCallBack
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^access-token/(?P<repo_id>[-0-9a-f]{36})/$', ExdrawAccessToken.as_view(), name='exdraw_access_token'),
|
||||
re_path(r'^upload-file/(?P<file_uuid>[-0-9a-f]{36})/$', ExdrawUploadFile.as_view(), name='exdraw_upload_file'),
|
||||
re_path(r'^download-link/(?P<file_uuid>[-0-9a-f]{36})/$', ExdrawDownloadLink.as_view(), name='exdraw_download_link'),
|
||||
re_path(r'^editor-status-callback/(?P<file_uuid>[-0-9a-f]{36})/$', ExdrawEditorCallBack.as_view(), name='exdraw_editor_callback'),
|
||||
]
|
120
seahub/exdraw/utils.py
Normal file
120
seahub/exdraw/utils.py
Normal file
@ -0,0 +1,120 @@
|
||||
import os
|
||||
import jwt
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
import posixpath
|
||||
|
||||
from seaserv import seafile_api
|
||||
|
||||
from seahub.tags.models import FileUUIDMap
|
||||
from seahub.settings import EXCALIDRAW_PRIVATE_KEY
|
||||
from seahub.utils import normalize_file_path, gen_file_get_url, gen_file_upload_url, gen_inner_file_get_url
|
||||
from seahub.utils.auth import AUTHORIZATION_PREFIX
|
||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
|
||||
from seahub.utils import uuid_str_to_36_chars
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def gen_exdraw_access_token(file_uuid, filename, username, permission='rw'):
|
||||
name = email2nickname(username)
|
||||
url, is_default, date_uploaded = api_avatar_url(username)
|
||||
access_token = jwt.encode({
|
||||
'file_uuid': file_uuid,
|
||||
'filename': filename,
|
||||
'username': username,
|
||||
'name': name,
|
||||
'avatar_url': url,
|
||||
'permission': permission,
|
||||
'exp': int(time.time()) + 86400 * 3, # 3 days
|
||||
},
|
||||
EXCALIDRAW_PRIVATE_KEY,
|
||||
algorithm='HS256'
|
||||
)
|
||||
return access_token
|
||||
|
||||
|
||||
def is_valid_exdraw_access_token(auth, file_uuid, return_payload=False):
|
||||
"""
|
||||
can decode a valid jwt payload
|
||||
"""
|
||||
is_valid, payload = False, None
|
||||
if not auth or auth[0].lower() not in AUTHORIZATION_PREFIX or len(auth) != 2:
|
||||
return (is_valid, payload) if return_payload else is_valid
|
||||
|
||||
token = auth[1]
|
||||
if not token or not file_uuid:
|
||||
return (is_valid, payload) if return_payload else is_valid
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, EXCALIDRAW_PRIVATE_KEY, algorithms=['HS256'])
|
||||
except Exception as e:
|
||||
logger.error('Failed to decode jwt: %s' % e)
|
||||
is_valid = False
|
||||
else:
|
||||
file_uuid_in_payload = payload.get('file_uuid')
|
||||
|
||||
if not file_uuid_in_payload:
|
||||
is_valid = False
|
||||
elif uuid_str_to_36_chars(file_uuid_in_payload) != uuid_str_to_36_chars(file_uuid):
|
||||
is_valid = False
|
||||
else:
|
||||
is_valid = True
|
||||
|
||||
if return_payload:
|
||||
return is_valid, payload
|
||||
return is_valid
|
||||
|
||||
|
||||
def get_exdraw_file_uuid(repo, path):
|
||||
repo_id = repo.repo_id
|
||||
if repo.is_virtual:
|
||||
repo_id = repo.origin_repo_id
|
||||
path = posixpath.join(repo.origin_path, path.strip('/'))
|
||||
|
||||
path = normalize_file_path(path)
|
||||
parent_dir = os.path.dirname(path)
|
||||
filename = os.path.basename(path)
|
||||
|
||||
uuid_map = FileUUIDMap.objects.get_or_create_fileuuidmap(
|
||||
repo_id, parent_dir, filename, is_dir=False)
|
||||
|
||||
file_uuid = str(uuid_map.uuid) # 36 chars str
|
||||
return file_uuid
|
||||
|
||||
|
||||
def get_exdraw_upload_link(uuid_map, last_modify_user=''):
|
||||
repo_id = uuid_map.repo_id
|
||||
parent_path = uuid_map.parent_path
|
||||
|
||||
obj_id = json.dumps({'online_office_update': True, 'parent_dir': parent_path})
|
||||
token = seafile_api.get_fileserver_access_token(
|
||||
repo_id, obj_id, 'update', last_modify_user, use_onetime=True)
|
||||
if not token:
|
||||
return None
|
||||
upload_link = gen_file_upload_url(token, 'update-api')
|
||||
return upload_link
|
||||
|
||||
|
||||
def get_exdraw_download_link(uuid_map, is_inner=False):
|
||||
repo_id = uuid_map.repo_id
|
||||
parent_path = uuid_map.parent_path
|
||||
filename = uuid_map.filename
|
||||
file_path = posixpath.join(parent_path, filename)
|
||||
|
||||
obj_id = seafile_api.get_file_id_by_path(repo_id, file_path)
|
||||
if not obj_id:
|
||||
return None
|
||||
token = seafile_api.get_fileserver_access_token(
|
||||
repo_id, obj_id, 'view', '', use_onetime=False)
|
||||
if not token:
|
||||
return None
|
||||
|
||||
if is_inner:
|
||||
download_link = gen_inner_file_get_url(token, filename)
|
||||
else:
|
||||
download_link = gen_file_get_url(token, filename)
|
||||
|
||||
return download_link
|
@ -3,7 +3,6 @@ import io
|
||||
import jwt
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
import logging
|
||||
import posixpath
|
||||
import shutil
|
||||
@ -22,26 +21,13 @@ from seahub.base.templatetags.seahub_tags import email2nickname
|
||||
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
|
||||
from seahub.seadoc.models import SeadocRevision
|
||||
from seahub.seadoc.settings import SDOC_REVISIONS_DIR, SDOC_IMAGES_DIR
|
||||
from seahub.utils import uuid_str_to_36_chars
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ZSDOC = 'sdoczip'
|
||||
|
||||
|
||||
def uuid_str_to_32_chars(file_uuid):
|
||||
if len(file_uuid) == 36:
|
||||
return uuid.UUID(file_uuid).hex
|
||||
else:
|
||||
return file_uuid
|
||||
|
||||
|
||||
def uuid_str_to_36_chars(file_uuid):
|
||||
if len(file_uuid) == 32:
|
||||
return str(uuid.UUID(file_uuid))
|
||||
else:
|
||||
return file_uuid
|
||||
|
||||
|
||||
def gen_seadoc_access_token(file_uuid, filename, username, permission='rw', default_title=None):
|
||||
name = email2nickname(username)
|
||||
url, is_default, date_uploaded = api_avatar_url(username)
|
||||
|
@ -287,6 +287,7 @@ INSTALLED_APPS = [
|
||||
'seahub.django_cas_ng',
|
||||
'seahub.seadoc',
|
||||
'seahub.subscription',
|
||||
'seahub.exdraw',
|
||||
]
|
||||
|
||||
|
||||
@ -962,6 +963,7 @@ ENABLE_WHITEBOARD = False
|
||||
##########################
|
||||
|
||||
ENABLE_EXCALIDRAW = False
|
||||
EXCALIDRAW_SERVER_URL = 'http://127.0.0.1:7070'
|
||||
|
||||
######################################
|
||||
# Settings for notification server #
|
||||
@ -1245,6 +1247,10 @@ SEADOC_PRIVATE_KEY = JWT_PRIVATE_KEY
|
||||
SEADOC_SERVER_URL = os.environ.get('SEADOC_SERVER_URL', '') or SEADOC_SERVER_URL
|
||||
FILE_CONVERTER_SERVER_URL = SEADOC_SERVER_URL.rstrip('/') + '/converter'
|
||||
|
||||
if os.environ.get('ENABLE_EXCALIDRAW', ''):
|
||||
ENABLE_EXCALIDRAW = os.environ.get('ENABLE_EXCALIDRAW', '').lower() == 'true'
|
||||
EXCALIDRAW_PRIVATE_KEY = JWT_PRIVATE_KEY
|
||||
|
||||
if os.environ.get('SITE_ROOT', ''):
|
||||
SITE_ROOT = os.environ.get('SITE_ROOT', '')
|
||||
SEAFILE_SERVER_PROTOCOL = os.environ.get('SEAFILE_SERVER_PROTOCOL', '')
|
||||
|
@ -12,6 +12,7 @@ docName: '{{ filename|escapejs }}',
|
||||
docUuid: '{{ file_uuid }}',
|
||||
lang: '{{ lang }}',
|
||||
rawPath: '{{ raw_path|escapejs }}',
|
||||
excalidrawServerUrl: '{{excalidraw_server_url}}'
|
||||
{% endblock %}
|
||||
|
||||
{% block render_bundle %}
|
||||
|
@ -1049,6 +1049,10 @@ if getattr(settings, 'ENABLE_SEADOC', False):
|
||||
re_path(r'^api/v2.1/seadoc/', include('seahub.seadoc.urls')),
|
||||
]
|
||||
|
||||
if getattr(settings, 'ENABLE_EXCALIDRAW', False):
|
||||
urlpatterns += [
|
||||
re_path(r'^api/v2.1/exdraw/', include('seahub.exdraw.urls')),
|
||||
]
|
||||
|
||||
if getattr(settings, 'CLIENT_SSO_VIA_LOCAL_BROWSER', False):
|
||||
urlpatterns += [
|
||||
|
@ -1497,3 +1497,17 @@ def transfer_repo(repo_id, new_owner, is_share, org_id=None):
|
||||
seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE)
|
||||
else:
|
||||
seafile_api.set_repo_owner(repo_id, new_owner)
|
||||
|
||||
|
||||
def uuid_str_to_32_chars(file_uuid):
|
||||
if len(file_uuid) == 36:
|
||||
return uuid.UUID(file_uuid).hex
|
||||
else:
|
||||
return file_uuid
|
||||
|
||||
|
||||
def uuid_str_to_36_chars(file_uuid):
|
||||
if len(file_uuid) == 32:
|
||||
return str(uuid.UUID(file_uuid))
|
||||
else:
|
||||
return file_uuid
|
||||
|
@ -69,6 +69,7 @@ from seahub.views import check_folder_permission, \
|
||||
from seahub.utils.repo import is_repo_owner, parse_repo_perm, is_repo_admin
|
||||
from seahub.group.utils import is_group_member
|
||||
from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_access_token, is_seadoc_revision
|
||||
from seahub.exdraw.utils import get_exdraw_file_uuid
|
||||
from seahub.seadoc.models import SeadocRevision
|
||||
|
||||
import seahub.settings as settings
|
||||
@ -78,7 +79,7 @@ from seahub.settings import FILE_ENCODING_LIST, FILE_PREVIEW_MAX_SIZE, \
|
||||
SHARE_LINK_FORCE_USE_PASSWORD, SHARE_LINK_PASSWORD_STRENGTH_LEVEL, \
|
||||
SHARE_LINK_EXPIRE_DAYS_DEFAULT, ENABLE_SHARE_LINK_REPORT_ABUSE, SEADOC_SERVER_URL, \
|
||||
ENABLE_METADATA_MANAGEMENT, BAIDU_MAP_KEY, GOOGLE_MAP_KEY, GOOGLE_MAP_ID, ENABLE_MULTIPLE_OFFICE_SUITE, \
|
||||
OFFICE_SUITE_LIST
|
||||
OFFICE_SUITE_LIST, EXCALIDRAW_SERVER_URL
|
||||
from seahub.constants import PERMISSION_INVISIBLE
|
||||
|
||||
# wopi
|
||||
@ -787,11 +788,13 @@ def view_lib_file(request, repo_id, path):
|
||||
return render(request, template, return_dict)
|
||||
|
||||
elif filetype == EXCALIDRAW:
|
||||
|
||||
file_uuid = get_exdraw_file_uuid(repo, path)
|
||||
return_dict['file_uuid'] = file_uuid
|
||||
return_dict['protocol'] = request.is_secure() and 'https' or 'http'
|
||||
return_dict['domain'] = get_current_site(request).domain
|
||||
return_dict['serviceUrl'] = get_service_url().rstrip('/')
|
||||
return_dict['language_code'] = get_language()
|
||||
return_dict['excalidraw_server_url'] = EXCALIDRAW_SERVER_URL
|
||||
return_dict['share_link_expire_days_Default'] = SHARE_LINK_EXPIRE_DAYS_DEFAULT
|
||||
return_dict['share_link_expire_days_min'] = SHARE_LINK_EXPIRE_DAYS_MIN
|
||||
return_dict['share_link_expire_days_max'] = SHARE_LINK_EXPIRE_DAYS_MAX
|
||||
|
Loading…
Reference in New Issue
Block a user