Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b8265d77f | ||
|
|
ceecc95c29 | ||
|
|
27c48446ce | ||
|
|
1d65c0e8d6 | ||
|
|
6b09c1ffac | ||
|
|
07d8e7e0b1 | ||
|
|
e80e41266a | ||
|
|
640d543487 | ||
|
|
aef3cee03c | ||
|
|
a9d843d885 | ||
|
|
bcc9d19916 | ||
|
|
23d2d40613 | ||
|
|
f873a4aeb9 | ||
|
|
a49495268f | ||
|
|
389b1bdd2e | ||
|
|
09c903ec2d | ||
|
|
b4c0455f70 | ||
|
|
6f59fe2b37 | ||
|
|
365853fade | ||
|
|
837ce742e0 | ||
|
|
11bf29fad7 | ||
|
|
350e898bda | ||
|
|
3c29788980 | ||
|
|
09ff58362b | ||
|
|
7cb6c148dd | ||
|
|
e8cca6ea15 | ||
|
|
a067234c64 | ||
|
|
b74463d9e5 | ||
|
|
28f85b4d45 | ||
|
|
e8567524ac | ||
|
|
c0dcee45d4 | ||
|
|
d51343ba8d | ||
|
|
777a1961d0 | ||
|
|
706cb057bc | ||
|
|
31c0356acc | ||
|
|
47de9769d0 | ||
|
|
ebb7ad0207 | ||
|
|
750c0983f1 | ||
|
|
f005466293 | ||
|
|
aacf9e05b9 | ||
|
|
62fba81406 | ||
|
|
c644757135 | ||
|
|
53a0dcce2f | ||
|
|
b1a948dd2a | ||
|
|
e19fdb2ad2 | ||
|
|
b9e2f2cf48 | ||
|
|
2de7e6b2d0 | ||
|
|
a2716ed8b8 | ||
|
|
73d60a0907 | ||
|
|
15c26a6e1f | ||
|
|
09ce9b811e | ||
|
|
05737663a2 | ||
|
|
9820a1e1f3 | ||
|
|
f4965c8256 | ||
|
|
4892083ee9 | ||
|
|
8f8295cb68 | ||
|
|
0be4513349 | ||
|
|
c7219a7516 | ||
|
|
f5704f470a | ||
|
|
9b19fd1e9d | ||
|
|
3e81d886f6 | ||
|
|
f1d31d1c10 | ||
|
|
44b811507d | ||
|
|
3c28140c32 | ||
|
|
a12148671a | ||
|
|
3a9199e176 | ||
|
|
3d5c444db0 | ||
|
|
6c385014cc | ||
|
|
f0a1c0ab06 | ||
|
|
4ebf15f3c1 | ||
|
|
b325a3e9a0 | ||
|
|
c3dd34dac9 | ||
|
|
3eceb3c719 | ||
|
|
f1cbde414f | ||
|
|
9bc02bdfcb | ||
|
|
e34a26c009 | ||
|
|
3fc032daa9 | ||
|
|
006554cd0a | ||
|
|
42468e9711 | ||
|
|
da5b04cdb3 | ||
|
|
66486b8810 | ||
|
|
c9883cafb9 | ||
|
|
a83b5fa489 | ||
|
|
49f44a00b2 | ||
|
|
807a860d07 | ||
|
|
3df445e98c | ||
|
|
e53abb7c79 | ||
|
|
4ca8bc06d8 | ||
|
|
76d4752776 | ||
|
|
95fcaada2b | ||
|
|
327af9f3fa | ||
|
|
066ae11ad7 | ||
|
|
f5c377dab8 | ||
|
|
963b7fdb82 | ||
|
|
8f66b5e04b | ||
|
|
0b98c6acc0 | ||
|
|
dfae93bbed | ||
|
|
1a440e1f67 | ||
|
|
270e2253a4 | ||
|
|
a9a3b524eb | ||
|
|
e4799f0045 | ||
|
|
9e6d2c1043 | ||
|
|
691fb2a380 | ||
|
|
f39bf33922 | ||
|
|
14ff3fea9a | ||
|
|
246829869a | ||
|
|
5f2fa2f033 | ||
|
|
6c52e87396 | ||
|
|
a4b0bb3e9c | ||
|
|
44b79ab946 | ||
|
|
1a39ed726d | ||
|
|
4c4fa9b2c7 | ||
|
|
708110c498 | ||
|
|
227efb5471 | ||
|
|
17cbb4b663 | ||
|
|
35ecc4c894 | ||
|
|
d976abdb84 | ||
|
|
bd78b87d6f | ||
|
|
a0869708aa | ||
|
|
bf53df1dc3 | ||
|
|
6f63e1c837 | ||
|
|
3e2234c1cd | ||
|
|
42208ac101 | ||
|
|
b247364d16 | ||
|
|
d8b86bd302 | ||
|
|
1ec66adda0 | ||
|
|
d3d0ee73a0 | ||
|
|
e757b471fd | ||
|
|
60c8843734 | ||
|
|
f779053040 | ||
|
|
6becac74d0 | ||
|
|
e64a73eb67 | ||
|
|
cffa82226d | ||
|
|
6626afa6b6 | ||
|
|
6d35063027 | ||
|
|
09d6db68ff | ||
|
|
f33145d02a | ||
|
|
a74e4f1a13 | ||
|
|
9344a831be | ||
|
|
4fd7e89ff8 | ||
|
|
d0a2774f65 | ||
|
|
fb563396fe | ||
|
|
73ad9f49ef | ||
|
|
10e6bf81f9 | ||
|
|
274b49d66f | ||
|
|
ab6749056a | ||
|
|
3be2e354c4 | ||
|
|
0c825c65d4 | ||
|
|
c6836f3654 | ||
|
|
c6a5db7740 | ||
|
|
179f34fad2 | ||
|
|
161096fbb2 | ||
|
|
69412300b6 | ||
|
|
329751350d | ||
|
|
90675e3ae8 | ||
|
|
238125eb8d | ||
|
|
d30f02b1c0 | ||
|
|
d9ec47f8ea |
@@ -8,3 +8,5 @@ tmp
|
||||
tank
|
||||
tank.exe
|
||||
|
||||
# mac
|
||||
.DS_Store
|
||||
@@ -9,10 +9,13 @@
|
||||
2. build/pack/build.sh
|
||||
VERSION_NAME=tank-x.x.x
|
||||
|
||||
3. core/config.go
|
||||
3. build/pack/cross-build.sh
|
||||
VERSION_NAME=tank-x.x.x
|
||||
|
||||
4. core/config.go
|
||||
VERSION = x.x.x
|
||||
|
||||
4. readme.md 修改 eyeblue/tank:x.x.x
|
||||
5. readme.md 修改 eyeblue/tank:x.x.x
|
||||
eyeblue/tank:x.x.x
|
||||
|
||||
|
||||
@@ -27,7 +30,7 @@ docker container prune
|
||||
# 构建新版本 (最后的.不能少)
|
||||
docker build -t eyeblue/tank:x.x.x .
|
||||
# 本地测试
|
||||
docker run --name dockermysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=tank -e MYSQL_USER=tank -e MYSQL_PASSWORD=tank123 -v ~/data/dockermysql:/var/lib/mysql -d mysql:5.7
|
||||
docker run --name dockermysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=tank -e MYSQL_USER=tank -e MYSQL_PASSWORD=tank123 -v ~/data/dockermysql:/var/lib/mysql -e TZ=Asia/Shanghai -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
docker run --name tank -p 6010:6010 --link dockermysql:mysql -v ~/data/dockermatter:/data/build/matter -d eyeblue/tank:x.x.x
|
||||
# 没问题后,推送到远程
|
||||
docker push eyeblue/tank:x.x.x
|
||||
@@ -47,6 +50,107 @@ c. 在github上发布新版本。
|
||||
|
||||
3.更新tank-doc 下载内容
|
||||
|
||||
## useful commands
|
||||
go get xxx/xxx
|
||||
go mod tidy
|
||||
|
||||
tank-4.1.1 2024-10-6
|
||||
1. 修复已知问题
|
||||
|
||||
tank-4.1.0 2024-7-27
|
||||
1. 删除用户后,空间一并删除。 #222
|
||||
2. 增加批量绑定空间成员功能。 #227
|
||||
3. 共享空间增加回收站逻辑。 #232
|
||||
4. 修复用户修改空间单位问题。 #233 #253
|
||||
5. 增强搜索功能,会进行dfs深度优先搜索。 #239
|
||||
|
||||
|
||||
1. When delete user, it's space will be deleted #222
|
||||
2. Batch creating space member #227
|
||||
3. Add feature of recycle bin for space #232
|
||||
4. Fix issue of changing unit #233 #253
|
||||
5. Enhance the search ability, using dfs #239
|
||||
|
||||
|
||||
tank-4.0.4 2024-4-29
|
||||
1. 修复已知问题
|
||||
|
||||
tank-4.0.3 2024-3-26
|
||||
1. 修复已知问题
|
||||
|
||||
tank-4.0.2 2023-11-10
|
||||
1. 修复BUG https://github.com/eyebluecn/tank/issues/229
|
||||
2. 新增了分享提取码快速提取
|
||||
3. 分享文件默认按照创建时间倒序排列。
|
||||
|
||||
1. Fix BUG https://github.com/eyebluecn/tank/issues/229
|
||||
2. Fast share using code.
|
||||
3. Share list order by create time desc.
|
||||
|
||||
|
||||
tank-4.0.1 2023-09-09
|
||||
1.修复一些文件扫描的bug https://github.com/eyebluecn/tank/issues/221 https://github.com/eyebluecn/tank/issues/220
|
||||
|
||||
1.Fix scan bug. https://github.com/eyebluecn/tank/issues/221 https://github.com/eyebluecn/tank/issues/220
|
||||
|
||||
|
||||
tank-4.0.0 2023-08-13
|
||||
1.增加空间概念,可以多人共同维护一个共享空间中的文件 #201
|
||||
2.新增粘贴上传文件功能 #188
|
||||
3.文件大小单位选择体验优化 #179
|
||||
4.支持使用url的方式抓取文件
|
||||
5.一些UI优化
|
||||
|
||||
1.Add space, supports multi users maintaining a shared space. #201
|
||||
2.Add the feature of uploading by pasting files. #188
|
||||
3.Human readable file size unit. #179
|
||||
4.Add the feature of uploading by url.
|
||||
6.Some user experience enhancement.
|
||||
|
||||
|
||||
tank-4.0.0.beta1 2023-07-22
|
||||
1. Shared space
|
||||
2. Copy and paste upload
|
||||
3. Crawl upload
|
||||
4. Space size selection control upgrade
|
||||
5. Some style modifications
|
||||
|
||||
|
||||
1. 共享空间
|
||||
2. 文件复制粘贴上传
|
||||
3. 文件抓取上传
|
||||
4. 空间大小选择控件升级
|
||||
5. 一些样式修改
|
||||
|
||||
|
||||
|
||||
tank-3.1.5 2022-03-21
|
||||
1. Support Sqlite.
|
||||
2. Upgrade webpack.
|
||||
|
||||
1. 支持Sqlite
|
||||
2. 升级了前端的webpack
|
||||
|
||||
|
||||
tank-3.1.4 2022-03-20
|
||||
1. Support Sqlite.
|
||||
2. Upgrade webpack.
|
||||
|
||||
1. 支持Sqlite
|
||||
2. 升级了前端的webpack
|
||||
|
||||
|
||||
|
||||
tank-3.1.3 2022-03-10
|
||||
1. Add kkfileview 3.x support
|
||||
2. Fix search bug https://github.com/eyebluecn/tank/issues/148
|
||||
3. Fix bug of totalSize in dashboard
|
||||
|
||||
1. 添加了kkfileview 3.x的预览支持
|
||||
2. 修复了一个搜索的bug https://github.com/eyebluecn/tank/issues/148
|
||||
3. 修复了文件大小统计的bug
|
||||
|
||||
|
||||
tank-3.1.2 2021-01-02
|
||||
1. Add scan manually once feature
|
||||
2. Add disable recycle bin feature
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# use golang 1.12
|
||||
FROM golang:1.12
|
||||
# use golang 1.22.5
|
||||
FROM golang:1.22.5
|
||||
|
||||
# maintainer. Author's name and email.
|
||||
MAINTAINER eyeblue "eyebluecn@126.com"
|
||||
@@ -14,10 +14,14 @@ COPY . /data
|
||||
VOLUME /data/build/matter
|
||||
|
||||
# proxy
|
||||
ENV GOPROXY=https://goproxy.io
|
||||
ENV GOPROXY=https://goproxy.cn
|
||||
|
||||
# Handle time-zone
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN apt-get install -y tzdata
|
||||
|
||||
# prepare the config file
|
||||
RUN go build -mod=readonly \
|
||||
RUN go build \
|
||||
&& cp -r /data/tank /data/build
|
||||
|
||||
# use 6010 as default.
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
[English Version](https://tank-doc.eyeblue.cn/en)
|
||||
|
||||
# 蓝眼云盘(3.1.2)
|
||||
[在线Demo](https://tank.eyeblue.cn) (体验账号: demo 密码:123456)
|
||||
# 蓝眼云盘(4.2.0)
|
||||
[在线Demo](https://tanker.eyeblue.cn) (体验账号: demo 密码:123456)
|
||||
|
||||
蓝眼云盘是蓝眼开源系列代表作品之一,致力于打造精致,优雅,简约的云盘。核心功能如下:
|
||||
1. 文件列表
|
||||
2. 文件/文件夹上传 + 拖拽上传
|
||||
2. 文件/文件夹上传 + 拖拽上传 + 分片/断点续传
|
||||
3. 共享空间
|
||||
3. 文件分享
|
||||
4. 文件监控统计
|
||||
5. 回收站
|
||||
@@ -15,6 +16,8 @@
|
||||
7. [WebDav](https://tank-doc.eyeblue.cn/advance/webdav.html)
|
||||
8. [扫描磁盘任务](https://tank-doc.eyeblue.cn/advance/scan.html)
|
||||
9. [在线预览及自定义配置预览引擎](https://tank-doc.eyeblue.cn/advance/preview.html)
|
||||
10. 支持Sqlite和Mysql数据库用以存储文件元信息,Sqlite可以做到开箱即用。
|
||||
|
||||
|
||||
[更多内容请移步至文档](https://tank-doc.eyeblue.cn/)
|
||||
|
||||
@@ -35,17 +38,34 @@
|
||||
|
||||
## [安装文档](https://tank-doc.eyeblue.cn/basic/install.html)
|
||||
|
||||
### 快速运行
|
||||
安装依赖
|
||||
```shell
|
||||
go mod tidy
|
||||
```
|
||||
运行main文件即可:
|
||||
```shell
|
||||
./main.go
|
||||
```
|
||||
|
||||
### Contribution
|
||||
|
||||
感谢所有蓝眼云盘的贡献者 [@zicla](https://github.com/zicla),[@seaheart](https://github.com/seaheart),[@yemuhe](https://github.com/yemuhe),[@hxsherry](https://github.com/hxsherry)
|
||||
|
||||
如果您也想参与进来,请尽情的fork, star, post issue, pull requests
|
||||
|
||||
当然你可以加入钉钉群一起直接交流,在钉钉群中可以获取最新beta版本。
|
||||
当然你可以加入钉钉群(一群已满员)一起直接交流,在钉钉群中可以获取最新beta版本。
|
||||
|
||||

|
||||

|
||||
|
||||
群号:44754005
|
||||
|
||||
|
||||
### Support
|
||||
IDE for this project is supported by [Jetbrains](https://jb.gg/OpenSourceSupport).
|
||||
|
||||
[](https://jb.gg/OpenSourceSupport)
|
||||
|
||||
群号:23156361
|
||||
|
||||
### License
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| master | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We strongly encourage you to report security vulnerabilities to
|
||||
our private security mailing list: security@cilium.io - first, before
|
||||
disclosing them in any public forums.
|
||||
|
||||
This is a private mailing list where only members of the Cilium internal
|
||||
security team are subscribed to, and is treated as top priority.
|
||||
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,38 +1,29 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.8aa2b3c6.chunk.css",
|
||||
"main.js": "/static/js/main.692308e3.chunk.js",
|
||||
"main.js.map": "/static/js/main.692308e3.chunk.js.map",
|
||||
"runtime-main.js": "/static/js/runtime-main.9bf84af1.js",
|
||||
"runtime-main.js.map": "/static/js/runtime-main.9bf84af1.js.map",
|
||||
"static/css/2.e86c2f44.chunk.css": "/static/css/2.e86c2f44.chunk.css",
|
||||
"static/js/2.789172e0.chunk.js": "/static/js/2.789172e0.chunk.js",
|
||||
"static/js/2.789172e0.chunk.js.map": "/static/js/2.789172e0.chunk.js.map",
|
||||
"main.css": "/static/css/main.3e2e293d.css",
|
||||
"main.js": "/static/js/main.1a935cc6.js",
|
||||
"static/js/833.2731ecbb.chunk.js": "/static/js/833.2731ecbb.chunk.js",
|
||||
"static/media/logo.png": "/static/media/logo.847e54ef7fb4b744fad4.png",
|
||||
"static/media/empty.svg": "/static/media/empty.1ac6f6fb3e1d8c985f32df632ded4fe1.svg",
|
||||
"static/media/psd.svg": "/static/media/psd.f2767f53369b5e75d7708a857d8381f4.svg",
|
||||
"static/media/xls.svg": "/static/media/xls.9f55e13113424a3cc3316acbf072f072.svg",
|
||||
"static/media/ppt.svg": "/static/media/ppt.8696321d8f3893a15681473769c8283c.svg",
|
||||
"static/media/doc.svg": "/static/media/doc.017d14ee10ef3d2427494d1b0e52b03e.svg",
|
||||
"static/media/pdf.svg": "/static/media/pdf.67d0061b2cef7912ed36b895cb8a328a.svg",
|
||||
"static/media/video.svg": "/static/media/video.09bf3324c0e699b3611bf6bb0fc1b50e.svg",
|
||||
"static/media/file.svg": "/static/media/file.b6be968e09dc04a45ec49a0dbcef53da.svg",
|
||||
"static/media/text.svg": "/static/media/text.fa66aadee4b7dd9d51f4d1c863e5f75e.svg",
|
||||
"static/media/audio.svg": "/static/media/audio.59dab6a9512494e5a34c8a424ab68f39.svg",
|
||||
"static/media/image.svg": "/static/media/image.ee35022875b37a7962afa08bce8bc1fd.svg",
|
||||
"static/media/folder.svg": "/static/media/folder.b4ce8a2960e8da846081c692d3065dff.svg",
|
||||
"static/media/archive.svg": "/static/media/archive.f58837716d1fc6f2e61dc1618dad27a6.svg",
|
||||
"index.html": "/index.html",
|
||||
"static/css/2.e86c2f44.chunk.css.map": "/static/css/2.e86c2f44.chunk.css.map",
|
||||
"static/css/main.8aa2b3c6.chunk.css.map": "/static/css/main.8aa2b3c6.chunk.css.map",
|
||||
"static/js/2.789172e0.chunk.js.LICENSE.txt": "/static/js/2.789172e0.chunk.js.LICENSE.txt",
|
||||
"static/media/archive.svg": "/static/media/archive.77d78eb7.svg",
|
||||
"static/media/audio.svg": "/static/media/audio.30a1ea02.svg",
|
||||
"static/media/default-skin.css": "/static/media/default-skin.b257fa9c.svg",
|
||||
"static/media/doc.svg": "/static/media/doc.1e70a87c.svg",
|
||||
"static/media/empty.svg": "/static/media/empty.babf4d08.svg",
|
||||
"static/media/file.svg": "/static/media/file.fd3e15f8.svg",
|
||||
"static/media/folder.svg": "/static/media/folder.f8d1b500.svg",
|
||||
"static/media/image.svg": "/static/media/image.06f036ce.svg",
|
||||
"static/media/logo.png": "/static/media/logo.21458adb.png",
|
||||
"static/media/pdf.svg": "/static/media/pdf.c256fdcb.svg",
|
||||
"static/media/ppt.svg": "/static/media/ppt.9ec3e018.svg",
|
||||
"static/media/psd.svg": "/static/media/psd.be6691be.svg",
|
||||
"static/media/text.svg": "/static/media/text.49dc13ae.svg",
|
||||
"static/media/video.svg": "/static/media/video.4387235d.svg",
|
||||
"static/media/xls.svg": "/static/media/xls.ef5ba31b.svg"
|
||||
"main.3e2e293d.css.map": "/static/css/main.3e2e293d.css.map",
|
||||
"main.1a935cc6.js.map": "/static/js/main.1a935cc6.js.map",
|
||||
"833.2731ecbb.chunk.js.map": "/static/js/833.2731ecbb.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.9bf84af1.js",
|
||||
"static/css/2.e86c2f44.chunk.css",
|
||||
"static/js/2.789172e0.chunk.js",
|
||||
"static/css/main.8aa2b3c6.chunk.css",
|
||||
"static/js/main.692308e3.chunk.js"
|
||||
"static/css/main.3e2e293d.css",
|
||||
"static/js/main.1a935cc6.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="eyeblue tank"/><title>EyeblueTank</title><link href="/static/css/2.e86c2f44.chunk.css" rel="stylesheet"><link href="/static/css/main.8aa2b3c6.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run EyeblueTank.</noscript><div id="root"></div><script>!function(e){function t(t){for(var n,f,l=t[0],a=t[1],i=t[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(t);s.length;)s.shift()();return u.push.apply(u,i||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var a=r[l];0!==o[a]&&(n=!1)}n&&(u.splice(t--,1),e=f(f.s=r[0]))}return e}var n={},o={1:0},u=[];function f(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,f),r.l=!0,r.exports}f.m=e,f.c=n,f.d=function(e,t,r){f.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,t){if(1&t&&(e=f(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(f.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)f.d(r,n,function(t){return e[t]}.bind(null,n));return r},f.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(t,"a",t),t},f.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},f.p="/";var l=this["webpackJsonptank-front"]=this["webpackJsonptank-front"]||[],a=l.push.bind(l);l.push=t,l=l.slice();for(var i=0;i<l.length;i++)t(l[i]);var p=a;r()}([])</script><script src="/static/js/2.789172e0.chunk.js"></script><script src="/static/js/main.692308e3.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="eyeblue tank"/><title>EyeblueTank</title><script defer="defer" src="/static/js/main.1a935cc6.js"></script><link href="/static/css/main.3e2e293d.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run EyeblueTank.</noscript><div id="root"></div></body></html>
|
||||
@@ -5,24 +5,16 @@ object-assign
|
||||
*/
|
||||
|
||||
/*!
|
||||
Copyright (c) 2017 Jed Watson.
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*! PhotoSwipe - v4.1.3 - 2019-01-08
|
||||
* http://photoswipe.com
|
||||
* Copyright (c) 2019 Dmitry Semenov; */
|
||||
|
||||
/*! PhotoSwipe Default UI - 4.1.3 - 2019-01-08
|
||||
* http://photoswipe.com
|
||||
* Copyright (c) 2019 Dmitry Semenov; */
|
||||
|
||||
/*! VelocityJS.org (1.5.2). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */
|
||||
|
||||
/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */
|
||||
|
||||
/** @license React v0.19.1
|
||||
/** @license React v0.20.2
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
@@ -31,15 +23,6 @@ object-assign
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
@@ -49,7 +32,25 @@ object-assign
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
/** @license React v17.0.2
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
@@ -57,3 +58,5 @@ object-assign
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
//! moment.js
|
||||
@@ -1,2 +0,0 @@
|
||||
!function(e){function webpackJsonpCallback(r){for(var n,o,u=r[0],c=r[1],p=r[2],i=0,l=[];i<u.length;i++)o=u[i],Object.prototype.hasOwnProperty.call(_,o)&&_[o]&&l.push(_[o][0]),_[o]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(a&&a(r);l.length;)l.shift()();return t.push.apply(t,p||[]),checkDeferredModules()}function checkDeferredModules(){for(var e,r=0;r<t.length;r++){for(var n=t[r],o=!0,u=1;u<n.length;u++){var a=n[u];0!==_[a]&&(o=!1)}o&&(t.splice(r--,1),e=__webpack_require__(__webpack_require__.s=n[0]))}return e}var r={},_={1:0},t=[];function __webpack_require__(_){if(r[_])return r[_].exports;var t=r[_]={i:_,l:!1,exports:{}};return e[_].call(t.exports,t,t.exports,__webpack_require__),t.l=!0,t.exports}__webpack_require__.m=e,__webpack_require__.c=r,__webpack_require__.d=function(e,r,_){__webpack_require__.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:_})},__webpack_require__.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.t=function(e,r){if(1&r&&(e=__webpack_require__(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var _=Object.create(null);if(__webpack_require__.r(_),Object.defineProperty(_,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var t in e)__webpack_require__.d(_,t,function(r){return e[r]}.bind(null,t));return _},__webpack_require__.n=function(e){var r=e&&e.__esModule?function getDefault(){return e.default}:function getModuleExports(){return e};return __webpack_require__.d(r,"a",r),r},__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},__webpack_require__.p="/";var n=this["webpackJsonptank-front"]=this["webpackJsonptank-front"]||[],o=n.push.bind(n);n.push=webpackJsonpCallback,n=n.slice();for(var u=0;u<n.length;u++)webpackJsonpCallback(n[u]);var a=o;checkDeferredModules()}([]);
|
||||
//# sourceMappingURL=runtime-main.9bf84af1.js.map
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1514189353878" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1197" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M9.367 0h992.97v341.333H9.366z" fill="#55C7F7" p-id="1198"></path><path d="M9.367 341.333h992.97v341.334H9.366z" fill="#F95F5D" p-id="1199"></path><path d="M9.367 682.667h992.97V1024H9.366z" fill="#7ECF3B" p-id="1200"></path><path d="M350.7 0h310.303v1024H350.7z" fill="#FDAF42" p-id="1201"></path><path d="M304.154 386.638V646.05h403.394V386.638H304.154z m356.849 215.97H350.7V429.46h310.303v173.15z" fill="#FFFFFF" p-id="1202"></path></svg>
|
||||
|
Before Width: | Height: | Size: 828 B |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1514189353878" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="1197" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M9.367 0h992.97v341.333H9.366z" fill="#55C7F7" p-id="1198"></path>
|
||||
<path d="M9.367 341.333h992.97v341.334H9.366z" fill="#F95F5D" p-id="1199"></path>
|
||||
<path d="M9.367 682.667h992.97V1024H9.366z" fill="#7ECF3B" p-id="1200"></path>
|
||||
<path d="M350.7 0h310.303v1024H350.7z" fill="#FDAF42" p-id="1201"></path>
|
||||
<path d="M304.154 386.638V646.05h403.394V386.638H304.154z m356.849 215.97H350.7V429.46h310.303v173.15z"
|
||||
fill="#FFFFFF" p-id="1202"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 855 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543321997742" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3887" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6C136 987.2 148 992 160 992h704c12 0 24.8-4.8 33.6-14.4C907.2 968 912 956 912 944V304L640 32H160z" fill="#FF5562" p-id="3888"></path><path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFBBC0" p-id="3889"></path><path d="M669.6 491.2c0-1.6 0.8-4 0-6.4V369.6c0-4.8-2.4-8.8-5.6-12-4-3.2-8-4-12.8-3.2l-250.4 70.4c-7.2 1.6-12 7.2-12 14.4v275.2c-9.6-4-20.8-6.4-32.8-6.4-8.8 0-17.6 0.8-26.4 3.2-40.8 11.2-66.4 43.2-58.4 72.8 6.4 23.2 30.4 37.6 60.8 37.6 8.8 0 17.6-1.6 26.4-3.2 36.8-10.4 60.8-36.8 60-63.2 0.8-1.6 0.8-3.2 0.8-5.6V570.4l220-61.6v136c-9.6-4-20.8-6.4-32.8-6.4-8.8 0-17.6 0.8-26.4 3.2-40.8 11.2-66.4 43.2-58.4 72.8C528 737.6 552 752 582.4 752c8.8 0 17.6-0.8 26.4-3.2 36-9.6 60-36 60-62.4 0.8-1.6 0.8-3.2 0.8-5.6V491.2z m-250.4 48v-53.6l220-61.6v53.6l-220 61.6z" fill="#FFFFFF" p-id="3890"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1543321997742" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="3887" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M160 32c-12 0-24.8 4.8-33.6 14.4S112 68 112 80v864c0 12 4.8 24.8 14.4 33.6C136 987.2 148 992 160 992h704c12 0 24.8-4.8 33.6-14.4C907.2 968 912 956 912 944V304L640 32H160z"
|
||||
fill="#FF5562" p-id="3888"></path>
|
||||
<path d="M912 304H688c-12 0-24.8-4.8-33.6-14.4-9.6-8.8-14.4-21.6-14.4-33.6V32l272 272z" fill="#FFBBC0"
|
||||
p-id="3889"></path>
|
||||
<path d="M669.6 491.2c0-1.6 0.8-4 0-6.4V369.6c0-4.8-2.4-8.8-5.6-12-4-3.2-8-4-12.8-3.2l-250.4 70.4c-7.2 1.6-12 7.2-12 14.4v275.2c-9.6-4-20.8-6.4-32.8-6.4-8.8 0-17.6 0.8-26.4 3.2-40.8 11.2-66.4 43.2-58.4 72.8 6.4 23.2 30.4 37.6 60.8 37.6 8.8 0 17.6-1.6 26.4-3.2 36.8-10.4 60.8-36.8 60-63.2 0.8-1.6 0.8-3.2 0.8-5.6V570.4l220-61.6v136c-9.6-4-20.8-6.4-32.8-6.4-8.8 0-17.6 0.8-26.4 3.2-40.8 11.2-66.4 43.2-58.4 72.8C528 737.6 552 752 582.4 752c8.8 0 17.6-0.8 26.4-3.2 36-9.6 60-36 60-62.4 0.8-1.6 0.8-3.2 0.8-5.6V491.2z m-250.4 48v-53.6l220-61.6v53.6l-220 61.6z"
|
||||
fill="#FFFFFF" p-id="3890"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg width="264" height="88" viewBox="0 0 264 88" xmlns="http://www.w3.org/2000/svg"><title>default-skin 2</title><g fill="none" fill-rule="evenodd"><g><path d="M67.002 59.5v3.768c-6.307.84-9.184 5.75-10.002 9.732 2.22-2.83 5.564-5.098 10.002-5.098V71.5L73 65.585 67.002 59.5z" id="Shape" fill="#fff"/><g fill="#fff"><path d="M13 29v-5h2v3h3v2h-5zM13 15h5v2h-3v3h-2v-5zM31 15v5h-2v-3h-3v-2h5zM31 29h-5v-2h3v-3h2v5z" id="Shape"/></g><g fill="#fff"><path d="M62 24v5h-2v-3h-3v-2h5zM62 20h-5v-2h3v-3h2v5zM70 20v-5h2v3h3v2h-5zM70 24h5v2h-3v3h-2v-5z"/></g><path d="M20.586 66l-5.656-5.656 1.414-1.414L22 64.586l5.656-5.656 1.414 1.414L23.414 66l5.656 5.656-1.414 1.414L22 67.414l-5.656 5.656-1.414-1.414L20.586 66z" fill="#fff"/><path d="M111.785 65.03L110 63.5l3-3.5h-10v-2h10l-3-3.5 1.785-1.468L117 59l-5.215 6.03z" fill="#fff"/><path d="M152.215 65.03L154 63.5l-3-3.5h10v-2h-10l3-3.5-1.785-1.468L147 59l5.215 6.03z" fill="#fff"/><g><path id="Rectangle-11" fill="#fff" d="M160.957 28.543l-3.25-3.25-1.413 1.414 3.25 3.25z"/><path d="M152.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" id="Oval-1" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M150 21h5v1h-5z"/></g><g><path d="M116.957 28.543l-1.414 1.414-3.25-3.25 1.414-1.414 3.25 3.25z" fill="#fff"/><path d="M108.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M106 21h5v1h-5z"/><path fill="#fff" d="M109.043 19.008l-.085 5-1-.017.085-5z"/></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1543300132402" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="2107" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M533.947184 38.36489h65.880106v88.119185c117.859409 0.660894 235.60867-1.211639 353.35793 0.660894 25.102953-2.533427 44.81962 16.962943 42.506492 42.07691 1.872533 216.993492-0.440596 434.097133 1.112505 651.090626-1.112505 22.250094 2.191965 46.923466-10.695466 66.640133-16.081751 11.455494-36.899909 10.023557-55.625236 10.904749-110.25913-0.550745-220.408111-0.330447-330.656225-0.330447v88.119185h-68.843113c-167.657764-30.621417-335.745108-58.709407-503.722304-88.119185-0.110149-256.977572 0-513.955144 0-770.822567 168.858388-29.519927 337.826924-58.268811 506.685311-88.339483z"
|
||||
fill="#2A5699" p-id="2108"></path>
|
||||
<path d="M599.816275 159.528769h363.491637v704.953477h-363.491637v-88.119185h286.38735v-44.059592h-286.38735v-55.07449h286.38735v-44.059593h-286.38735v-55.07449h286.38735v-44.059592h-286.38735v-55.074491h286.38735v-44.059592h-286.38735v-55.074491h286.38735v-44.059592h-286.38735v-55.07449h286.38735v-44.059593h-286.38735v-77.104286zM254.829667 354.162018c20.928306-1.211639 41.856613-2.092831 62.784919-3.19432 14.649814 74.350562 29.619061 148.590975 45.491529 222.61109 12.446835-76.443393 26.215457-152.666487 39.543484-228.999731 22.029796-0.771043 44.059592-1.982682 65.97924-3.30447-24.89367 106.734362-46.703168 214.349917-73.909966 320.423385-18.39488 9.582961-45.932125-0.440596-67.741623 1.10149-14.649814-72.90761-31.722906-145.38564-44.819621-218.645727-12.898446 71.156242-29.641091 141.651589-44.390039 212.367235a4187.754101 4187.754101 0 0 1-63.666111-3.866229c-18.284731-96.931103-39.763782-193.190297-56.836874-290.341698 18.835476-0.881192 37.792115-1.652235 56.627591-2.313129 11.33433 70.164901 24.221761 139.999355 34.146184 210.274404 15.508976-72.037433 31.37043-144.074867 46.791287-216.1123z"
|
||||
fill="#FFFFFF" p-id="2109"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543300132402" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2107" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M533.947184 38.36489h65.880106v88.119185c117.859409 0.660894 235.60867-1.211639 353.35793 0.660894 25.102953-2.533427 44.81962 16.962943 42.506492 42.07691 1.872533 216.993492-0.440596 434.097133 1.112505 651.090626-1.112505 22.250094 2.191965 46.923466-10.695466 66.640133-16.081751 11.455494-36.899909 10.023557-55.625236 10.904749-110.25913-0.550745-220.408111-0.330447-330.656225-0.330447v88.119185h-68.843113c-167.657764-30.621417-335.745108-58.709407-503.722304-88.119185-0.110149-256.977572 0-513.955144 0-770.822567 168.858388-29.519927 337.826924-58.268811 506.685311-88.339483z" fill="#2A5699" p-id="2108"></path><path d="M599.816275 159.528769h363.491637v704.953477h-363.491637v-88.119185h286.38735v-44.059592h-286.38735v-55.07449h286.38735v-44.059593h-286.38735v-55.07449h286.38735v-44.059592h-286.38735v-55.074491h286.38735v-44.059592h-286.38735v-55.074491h286.38735v-44.059592h-286.38735v-55.07449h286.38735v-44.059593h-286.38735v-77.104286zM254.829667 354.162018c20.928306-1.211639 41.856613-2.092831 62.784919-3.19432 14.649814 74.350562 29.619061 148.590975 45.491529 222.61109 12.446835-76.443393 26.215457-152.666487 39.543484-228.999731 22.029796-0.771043 44.059592-1.982682 65.97924-3.30447-24.89367 106.734362-46.703168 214.349917-73.909966 320.423385-18.39488 9.582961-45.932125-0.440596-67.741623 1.10149-14.649814-72.90761-31.722906-145.38564-44.819621-218.645727-12.898446 71.156242-29.641091 141.651589-44.390039 212.367235a4187.754101 4187.754101 0 0 1-63.666111-3.866229c-18.284731-96.931103-39.763782-193.190297-56.836874-290.341698 18.835476-0.881192 37.792115-1.652235 56.627591-2.313129 11.33433 70.164901 24.221761 139.999355 34.146184 210.274404 15.508976-72.037433 31.37043-144.074867 46.791287-216.1123z" fill="#FFFFFF" p-id="2109"></path></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1582644951614" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3842"
|
||||
width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M692.906667 385.024c-11.605333-2.730667-23.210667-6.144-34.816-11.605333l-63.488 25.258666-5.461334 2.048h-5.461333c-8.874667 0-17.749333-4.096-23.210667-11.605333-5.461333-7.509333-6.826667-17.066667-4.778666-25.258667l8.874666-30.72-97.621333-19.797333v400.725333L901.12 402.090667l-105.130667-21.162667c-18.432 5.461333-38.229333 8.874667-59.392 8.874667-13.653333 0-28.672-1.365333-43.690666-4.778667z"
|
||||
fill="#A6DAFC" p-id="3843"></path>
|
||||
<path d="M512 502.442667v467.626666l389.802667-100.352V402.090667zM111.274667 413.696l356.352-100.352V580.266667z"
|
||||
fill="#568EC8" p-id="3844"></path>
|
||||
<path d="M111.274667 413.696v456.021333L512 970.069333V502.442667z" fill="#A6DAFC" p-id="3845"></path>
|
||||
<path d="M512 502.442667l389.802667-100.352L1024 546.816 645.802667 658.090667z" fill="#78C5FD" p-id="3846"></path>
|
||||
<path d="M111.274667 413.696l400.725333 88.746667-122.197333 167.253333L0 569.344z" fill="#CFF0FF"
|
||||
p-id="3847"></path>
|
||||
<path d="M886.784 160.426667c-2.730667-6.826667-6.144-13.653333-9.557333-20.48-1.365333-2.048-2.730667-4.096-4.096-6.826667-4.096-6.144-8.192-12.970667-13.653334-18.432s-10.24-11.605333-16.384-16.384c-2.048-2.048-4.096-3.413333-6.144-4.778667-8.192-6.826667-17.066667-12.288-25.941333-17.749333-2.048-1.365333-4.778667-2.730667-6.826667-3.413333-4.778667-2.048-9.557333-4.778667-15.018666-6.144-7.509333-2.730667-15.701333-5.461333-23.893334-6.826667-2.730667-0.682667-6.144-1.365333-8.874666-2.048-0.682667 0-2.048 0-2.730667-0.682667-2.048 0-4.096-0.682667-6.144-0.682666-1.365333 0-2.048 0-3.413333-0.682667-2.048 0-3.413333 0-5.461334-0.682667H727.04c-4.096 0-8.192 0-11.605333 0.682667h-2.048c-4.096 0.682667-8.192 0.682667-12.288 1.365333h-0.682667c-4.096 0.682667-7.509333 1.365333-11.605333 2.048-0.682667 0-1.365333 0-1.365334 0.682667-4.096 0.682667-7.509333 2.048-10.922666 3.413333-0.682667 0-1.365333 0.682667-2.048 0.682667-3.413333 1.365333-7.509333 2.730667-10.922667 4.096-0.682667 0-0.682667 0.682667-1.365333 0.682667-3.413333 1.365333-6.826667 3.413333-10.24 4.778666 0 0-0.682667 0-0.682667 0.682667-3.413333 2.048-6.826667 3.413333-10.24 5.461333-0.682667 0.682667-1.365333 0.682667-2.048 1.365334-3.413333 2.048-6.144 4.096-9.557333 6.144-0.682667 0.682667-1.365333 0.682667-1.365334 1.365333-3.413333 2.048-6.144 4.778667-8.874666 7.509333l-8.192 8.192-1.365334 1.365334c-2.730667 2.730667-4.778667 5.461333-7.509333 8.192-0.682667 0.682667-0.682667 1.365333-1.365333 1.365333-2.048 2.730667-4.778667 6.144-6.826667 8.874667 0 0.682667-0.682667 0.682667-0.682667 1.365333-2.048 3.413333-4.096 6.144-5.461333 9.557333 0 0.682667-0.682667 0.682667-0.682667 1.365334-2.048 3.413333-3.413333 6.826667-4.778666 10.24 0 0.682667-0.682667 1.365333-0.682667 2.048-1.365333 3.413333-2.730667 6.826667-4.096 10.922666 0 0.682667-0.682667 1.365333-0.682667 2.048-1.365333 4.096-2.048 7.509333-3.413333 11.605334-10.24 46.421333 2.730667 92.16 32.085333 127.658666L593.92 339.968l-9.557333 32.768v0.682667h0.682666l58.709334-23.210667 16.384-6.826667c6.144 3.413333 12.970667 6.144 19.797333 8.192s13.653333 4.778667 21.162667 6.144c2.730667 0.682667 6.144 1.365333 8.874666 2.048 0.682667 0 2.048 0 2.730667 0.682667 2.048 0 4.096 0.682667 6.144 0.682667 1.365333 0 2.048 0 3.413333 0.682666 2.048 0 3.413333 0 5.461334 0.682667H739.328c4.096 0 8.192 0 11.605333-0.682667h2.048c4.096-0.682667 8.192-0.682667 12.288-1.365333h0.682667c4.096-0.682667 7.509333-1.365333 11.605333-2.048 0.682667 0 1.365333 0 2.048-0.682667 4.096-0.682667 7.509333-2.048 10.922667-3.413333 0.682667 0 1.365333-0.682667 2.048-0.682667 3.413333-1.365333 7.509333-2.730667 10.922667-4.096 0.682667 0 0.682667-0.682667 1.365333-0.682666 3.413333-1.365333 6.826667-3.413333 10.24-4.778667 0 0 0.682667 0 0.682667-0.682667 3.413333-2.048 6.826667-3.413333 10.24-5.461333 0.682667-0.682667 1.365333-0.682667 2.048-1.365333 3.413333-2.048 6.144-4.096 9.557333-6.144 0.682667-0.682667 1.365333-0.682667 1.365333-1.365334 3.413333-2.048 6.144-4.778667 8.874667-7.509333l8.192-8.192 1.365333-1.365333c2.730667-2.730667 4.778667-5.461333 7.509334-8.192 0.682667-0.682667 0.682667-1.365333 1.365333-2.048 2.048-2.730667 4.778667-6.144 6.826667-8.874667 0-0.682667 0.682667-0.682667 0.682666-1.365333 2.048-3.413333 4.096-6.144 5.461334-9.557334 0-0.682667 0.682667-0.682667 0.682666-1.365333 2.048-3.413333 3.413333-6.826667 4.778667-10.24 0-0.682667 0.682667-1.365333 0.682667-2.048 1.365333-3.413333 2.730667-6.826667 4.096-10.922667 0-0.682667 0.682667-1.365333 0.682666-2.048 1.365333-4.096 2.048-7.509333 3.413334-11.605333 5.461333-28.672 2.730667-57.344-6.826667-83.285333z"
|
||||
fill="#F9DA3E" p-id="3848"></path>
|
||||
<path d="M739.328 167.936h48.469333v27.989333h-13.653333v-15.701333h-83.968v15.701333h-13.653333v-27.989333h48.469333c-1.365333-3.413333-3.413333-6.826667-4.778667-10.24l14.336-2.048c2.048 3.413333 3.413333 7.509333 4.778667 12.288z m37.546667 52.565333v12.288h-37.546667v25.258667h48.469333v12.970667H677.205333v-12.970667h48.469334v-25.258667h-37.546667v-12.288h88.746667z m-51.882667-24.576c-10.24 8.874667-22.528 16.384-37.546667 22.528l-5.461333-12.288c14.336-5.461333 25.941333-11.605333 34.816-19.797333l8.192 9.557333z m56.661333 10.922667l-7.509333 11.605333c-11.605333-9.557333-23.210667-17.066667-35.498667-22.528l6.826667-10.24c13.653333 6.144 25.258667 12.970667 36.181333 21.162667z"
|
||||
fill="#FFFFFF" p-id="3849"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1514189511483" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="6709" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M676.297143 0H145.609143C130.834286 0 118.857143 11.977143 118.857143 35.218286V1005.714286c0 6.308571 11.977143 18.285714 26.752 18.285714h732.781714c14.774857 0 26.752-11.977143 26.752-18.285714V237.312c0-12.726857-1.700571-16.822857-4.699428-19.84L687.670857 4.699429A16.164571 16.164571 0 0 0 676.297143 0z"
|
||||
fill="#E9E9E0" p-id="6710"></path>
|
||||
<path d="M685.714286 2.761143V219.428571h216.667428z" fill="#D9D7CA" p-id="6711"></path>
|
||||
<path d="M878.390857 1024H145.609143A26.752 26.752 0 0 1 118.857143 997.248V713.142857h786.285714v284.105143c0 14.774857-11.977143 26.752-26.752 26.752z"
|
||||
fill="#C8BDB8" p-id="6712"></path>
|
||||
<path d="M338.285714 859.428571m-54.857143 0a54.857143 54.857143 0 1 0 109.714286 0 54.857143 54.857143 0 1 0-109.714286 0Z"
|
||||
fill="#FFFFFF" p-id="6713"></path>
|
||||
<path d="M521.142857 859.428571m-54.857143 0a54.857143 54.857143 0 1 0 109.714286 0 54.857143 54.857143 0 1 0-109.714286 0Z"
|
||||
fill="#FFFFFF" p-id="6714"></path>
|
||||
<path d="M704 859.428571m-54.857143 0a54.857143 54.857143 0 1 0 109.714286 0 54.857143 54.857143 0 1 0-109.714286 0Z"
|
||||
fill="#FFFFFF" p-id="6715"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1514189511483" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6709" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M676.297143 0H145.609143C130.834286 0 118.857143 11.977143 118.857143 35.218286V1005.714286c0 6.308571 11.977143 18.285714 26.752 18.285714h732.781714c14.774857 0 26.752-11.977143 26.752-18.285714V237.312c0-12.726857-1.700571-16.822857-4.699428-19.84L687.670857 4.699429A16.164571 16.164571 0 0 0 676.297143 0z" fill="#E9E9E0" p-id="6710"></path><path d="M685.714286 2.761143V219.428571h216.667428z" fill="#D9D7CA" p-id="6711"></path><path d="M878.390857 1024H145.609143A26.752 26.752 0 0 1 118.857143 997.248V713.142857h786.285714v284.105143c0 14.774857-11.977143 26.752-26.752 26.752z" fill="#C8BDB8" p-id="6712"></path><path d="M338.285714 859.428571m-54.857143 0a54.857143 54.857143 0 1 0 109.714286 0 54.857143 54.857143 0 1 0-109.714286 0Z" fill="#FFFFFF" p-id="6713"></path><path d="M521.142857 859.428571m-54.857143 0a54.857143 54.857143 0 1 0 109.714286 0 54.857143 54.857143 0 1 0-109.714286 0Z" fill="#FFFFFF" p-id="6714"></path><path d="M704 859.428571m-54.857143 0a54.857143 54.857143 0 1 0 109.714286 0 54.857143 54.857143 0 1 0-109.714286 0Z" fill="#FFFFFF" p-id="6715"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1514189634329" class="icon" style="" viewBox="0 0 1219 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="10648" width="238.0859375" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M1197.787429 327.192381a19.504762 19.504762 0 0 1 19.553523 19.407238v614.156191c0 34.913524-28.574476 63.24419-63.878095 63.24419H63.878095C28.623238 1024 0 995.718095 0 960.75581V346.599619a19.504762 19.504762 0 0 1 19.602286-19.358476h1178.185143z m-66.80381-213.089524C1179.550476 114.102857 1219.047619 153.209905 1219.047619 201.386667v80.213333H1.657905V88.259048C1.657905 39.497143 41.593905 0 90.794667 0h290.864762c17.408 0 33.987048 7.655619 45.104761 20.967619l78.214096 93.135238h626.005333z"
|
||||
fill="#23B7E5" p-id="10649"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 949 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1514189634329" class="icon" style="" viewBox="0 0 1219 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10648" xmlns:xlink="http://www.w3.org/1999/xlink" width="238.0859375" height="200"><defs><style type="text/css"></style></defs><path d="M1197.787429 327.192381a19.504762 19.504762 0 0 1 19.553523 19.407238v614.156191c0 34.913524-28.574476 63.24419-63.878095 63.24419H63.878095C28.623238 1024 0 995.718095 0 960.75581V346.599619a19.504762 19.504762 0 0 1 19.602286-19.358476h1178.185143z m-66.80381-213.089524C1179.550476 114.102857 1219.047619 153.209905 1219.047619 201.386667v80.213333H1.657905V88.259048C1.657905 39.497143 41.593905 0 90.794667 0h290.864762c17.408 0 33.987048 7.655619 45.104761 20.967619l78.214096 93.135238h626.005333z" fill="#23B7E5" p-id="10649"></path></svg>
|
||||
|
Before Width: | Height: | Size: 943 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1514190539077" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14917" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M128 0h597.3l227.6 227.9v739.2c0 31.4-25.5 56.9-56.9 56.9H128c-31.4 0-56.9-25.5-56.9-56.9V56.9C71.1 25.5 96.6 0 128 0z" fill="#13BE93" p-id="14918"></path><path d="M630.5 1024H128c-25.7 0-47.4-17-54.4-40.4l218.1-218.1c22.2-22.2 58.2-22.2 80.5 0L630.5 1024z" fill="#6BD8BD" p-id="14919"></path><path d="M952.9 858v109.1c0 31.4-25.5 56.9-56.9 56.9H322.4l-4.7-4.7 358-358c22.2-22.2 58.2-22.2 80.5 0L952.9 858z" fill="#9CF9E2" p-id="14920"></path><path d="M725.3 0l227.6 227.6H782.2c-31.4 0-56.9-25.5-56.9-56.9V0z" fill="#69DCC0" p-id="14921"></path><path d="M327.1 483.6m-85.3 0a85.3 85.3 0 1 0 170.6 0 85.3 85.3 0 1 0-170.6 0Z" fill="#E9FFFA" p-id="14922"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1514190539077" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="14917" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M128 0h597.3l227.6 227.9v739.2c0 31.4-25.5 56.9-56.9 56.9H128c-31.4 0-56.9-25.5-56.9-56.9V56.9C71.1 25.5 96.6 0 128 0z"
|
||||
fill="#13BE93" p-id="14918"></path>
|
||||
<path d="M630.5 1024H128c-25.7 0-47.4-17-54.4-40.4l218.1-218.1c22.2-22.2 58.2-22.2 80.5 0L630.5 1024z"
|
||||
fill="#6BD8BD" p-id="14919"></path>
|
||||
<path d="M952.9 858v109.1c0 31.4-25.5 56.9-56.9 56.9H322.4l-4.7-4.7 358-358c22.2-22.2 58.2-22.2 80.5 0L952.9 858z"
|
||||
fill="#9CF9E2" p-id="14920"></path>
|
||||
<path d="M725.3 0l227.6 227.6H782.2c-31.4 0-56.9-25.5-56.9-56.9V0z" fill="#69DCC0" p-id="14921"></path>
|
||||
<path d="M327.1 483.6m-85.3 0a85.3 85.3 0 1 0 170.6 0 85.3 85.3 0 1 0-170.6 0Z" fill="#E9FFFA" p-id="14922"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1514284130163" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="1551" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M915.114667 1024H108.885333V0h587.776v218.112H914.773333V1024z m-785.066667-20.821333h763.904V238.933333H675.84V20.821333H129.706667v982.357334z"
|
||||
fill="#F90E09" p-id="1552"></path>
|
||||
<path d="M708.266667 10.581333L904.533333 206.848h-196.266666z" fill="#F90E09" p-id="1553"></path>
|
||||
<path d="M282.965333 792.576c33.450667-23.210667 55.637333-57.344 76.8-91.136-30.378667 25.941333-59.050667 54.954667-76.8 91.136z m396.629334-169.642667c31.402667 15.018667 65.877333 31.061333 101.376 21.504-24.576-29.696-67.584-21.504-101.376-21.504z m-246.101334 23.210667c45.397333-16.725333 91.818667-29.696 138.922667-39.594667-30.378667-26.965333-55.978667-58.368-76.458667-93.525333-16.725333 46.08-38.912 90.112-62.464 133.12z m67.925334-243.370667c14.677333-37.546667 19.114667-83.285333-5.802667-117.418666-7.168 38.912-4.096 79.189333 5.802667 117.418666z m-45.397334-105.130666c4.096-24.917333 35.84-29.696 55.637334-22.186667 12.288 7.850667 18.432 22.869333 18.773333 37.205333 1.365333 44.032-6.485333 87.381333-12.629333 130.730667-3.413333 20.138667 8.874667 38.229333 17.749333 55.296 21.845333 40.277333 52.224 76.458667 92.16 99.669333 48.128-5.802667 98.645333-12.970667 145.749333 2.389334 35.157333 11.946667 26.965333 77.141333-11.264 77.824-53.589333 2.730667-102.058667-23.893333-150.186666-43.349334-66.218667 4.778667-130.730667 24.917333-194.218667 44.032-28.672 46.762667-54.613333 98.986667-101.034667 130.730667-23.210667 18.090667-76.458667 3.072-64.512-32.085333 20.138667-49.152 70.314667-76.117333 114.346667-100.693334 12.629333-6.485333 19.797333-18.773333 26.282667-31.061333 30.037333-57.685333 60.074667-115.370667 84.309333-175.786667-12.629333-56.661333-31.061333-114.688-21.162667-172.714666z"
|
||||
fill="#F90E09" p-id="1554"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1514284130163" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1551" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M915.114667 1024H108.885333V0h587.776v218.112H914.773333V1024z m-785.066667-20.821333h763.904V238.933333H675.84V20.821333H129.706667v982.357334z" fill="#F90E09" p-id="1552"></path><path d="M708.266667 10.581333L904.533333 206.848h-196.266666z" fill="#F90E09" p-id="1553"></path><path d="M282.965333 792.576c33.450667-23.210667 55.637333-57.344 76.8-91.136-30.378667 25.941333-59.050667 54.954667-76.8 91.136z m396.629334-169.642667c31.402667 15.018667 65.877333 31.061333 101.376 21.504-24.576-29.696-67.584-21.504-101.376-21.504z m-246.101334 23.210667c45.397333-16.725333 91.818667-29.696 138.922667-39.594667-30.378667-26.965333-55.978667-58.368-76.458667-93.525333-16.725333 46.08-38.912 90.112-62.464 133.12z m67.925334-243.370667c14.677333-37.546667 19.114667-83.285333-5.802667-117.418666-7.168 38.912-4.096 79.189333 5.802667 117.418666z m-45.397334-105.130666c4.096-24.917333 35.84-29.696 55.637334-22.186667 12.288 7.850667 18.432 22.869333 18.773333 37.205333 1.365333 44.032-6.485333 87.381333-12.629333 130.730667-3.413333 20.138667 8.874667 38.229333 17.749333 55.296 21.845333 40.277333 52.224 76.458667 92.16 99.669333 48.128-5.802667 98.645333-12.970667 145.749333 2.389334 35.157333 11.946667 26.965333 77.141333-11.264 77.824-53.589333 2.730667-102.058667-23.893333-150.186666-43.349334-66.218667 4.778667-130.730667 24.917333-194.218667 44.032-28.672 46.762667-54.613333 98.986667-101.034667 130.730667-23.210667 18.090667-76.458667 3.072-64.512-32.085333 20.138667-49.152 70.314667-76.117333 114.346667-100.693334 12.629333-6.485333 19.797333-18.773333 26.282667-31.061333 30.037333-57.685333 60.074667-115.370667 84.309333-175.786667-12.629333-56.661333-31.061333-114.688-21.162667-172.714666z" fill="#F90E09" p-id="1554"></path></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1543300178225" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="5733" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M537.372818 38.36489h62.454472v99.134083c117.738246 0.660894 235.60867-1.211639 353.236766 0.550745 25.334266-2.423278 45.172097 17.29339 42.627656 42.627655 1.872533 205.758296-0.440596 411.626741 1.10149 617.506201-1.10149 22.250094 2.20298 46.923466-10.574302 66.640134-16.081751 11.664777-36.999043 10.133706-55.834519 11.014898-110.148981-0.550745-220.297962-0.330447-330.557091-0.330447v110.148981h-68.523681c-167.856032-30.621417-336.06454-58.599258-504.041736-88.119185-0.110149-256.977572 0-513.955144 0-770.822567 169.970892-29.420793 339.93077-59.381316 510.110945-88.350498z"
|
||||
fill="#D24625" p-id="5734"></path>
|
||||
<path d="M599.82729 170.543667h363.491636v671.908783h-363.491636v-88.119185h264.357554v-44.059592h-264.357554v-55.074491h264.357554v-44.059592h-264.247405c-0.110149-21.5892-0.110149-43.1784-0.231313-64.767601 43.630011 13.53731 93.307202 13.217878 132.299941-13.008594 42.18706-25.003819 64.216856-72.698327 67.851772-120.271672-48.366417-0.330447-96.710805-0.220298-144.956059-0.220298-0.121164-47.914807 0.550745-95.829613-0.991341-143.634271-18.075448 3.524767-36.128866 7.269833-54.083149 11.125047v-109.818534z"
|
||||
fill="#FFFFFF" p-id="5735"></path>
|
||||
<path d="M677.041725 246.216017c76.553542 3.524767 140.880546 67.961921 144.956059 144.295165-48.366417 0.550745-96.710805 0.330447-145.066208 0.330447 0-48.245254-0.121164-96.490507 0.110149-144.625612z"
|
||||
fill="#D24625" p-id="5736"></path>
|
||||
<path d="M203.940838 329.378497c59.810897 2.863874 132.288926-23.682031 181.855967 21.148605 47.485226 57.607917 34.917227 163.571236-35.467972 198.047867-25.003819 12.777282-53.642554 11.014898-80.849351 10.133706-0.110149 43.068251-0.220298 86.136503-0.110149 129.19374-21.820513-1.872533-43.74016-3.745065-65.538644-5.507449-0.980326-117.628097-1.211639-235.377357 0.110149-353.016469z"
|
||||
fill="#FFFFFF" p-id="5737"></path>
|
||||
<path d="M269.380347 388.969096c21.699349-0.991341 48.575701-4.956704 63.214501 15.420857 12.567999 21.5892 11.89609 50.007637 1.442951 72.257732-12.567999 22.69069-40.986436 20.48771-62.895068 23.131286-2.313129-36.899909-2.092831-73.799817-1.762384-110.809875z"
|
||||
fill="#D24625" p-id="5738"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543300178225" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5733" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M537.372818 38.36489h62.454472v99.134083c117.738246 0.660894 235.60867-1.211639 353.236766 0.550745 25.334266-2.423278 45.172097 17.29339 42.627656 42.627655 1.872533 205.758296-0.440596 411.626741 1.10149 617.506201-1.10149 22.250094 2.20298 46.923466-10.574302 66.640134-16.081751 11.664777-36.999043 10.133706-55.834519 11.014898-110.148981-0.550745-220.297962-0.330447-330.557091-0.330447v110.148981h-68.523681c-167.856032-30.621417-336.06454-58.599258-504.041736-88.119185-0.110149-256.977572 0-513.955144 0-770.822567 169.970892-29.420793 339.93077-59.381316 510.110945-88.350498z" fill="#D24625" p-id="5734"></path><path d="M599.82729 170.543667h363.491636v671.908783h-363.491636v-88.119185h264.357554v-44.059592h-264.357554v-55.074491h264.357554v-44.059592h-264.247405c-0.110149-21.5892-0.110149-43.1784-0.231313-64.767601 43.630011 13.53731 93.307202 13.217878 132.299941-13.008594 42.18706-25.003819 64.216856-72.698327 67.851772-120.271672-48.366417-0.330447-96.710805-0.220298-144.956059-0.220298-0.121164-47.914807 0.550745-95.829613-0.991341-143.634271-18.075448 3.524767-36.128866 7.269833-54.083149 11.125047v-109.818534z" fill="#FFFFFF" p-id="5735"></path><path d="M677.041725 246.216017c76.553542 3.524767 140.880546 67.961921 144.956059 144.295165-48.366417 0.550745-96.710805 0.330447-145.066208 0.330447 0-48.245254-0.121164-96.490507 0.110149-144.625612z" fill="#D24625" p-id="5736"></path><path d="M203.940838 329.378497c59.810897 2.863874 132.288926-23.682031 181.855967 21.148605 47.485226 57.607917 34.917227 163.571236-35.467972 198.047867-25.003819 12.777282-53.642554 11.014898-80.849351 10.133706-0.110149 43.068251-0.220298 86.136503-0.110149 129.19374-21.820513-1.872533-43.74016-3.745065-65.538644-5.507449-0.980326-117.628097-1.211639-235.377357 0.110149-353.016469z" fill="#FFFFFF" p-id="5737"></path><path d="M269.380347 388.969096c21.699349-0.991341 48.575701-4.956704 63.214501 15.420857 12.567999 21.5892 11.89609 50.007637 1.442951 72.257732-12.567999 22.69069-40.986436 20.48771-62.895068 23.131286-2.313129-36.899909-2.092831-73.799817-1.762384-110.809875z" fill="#D24625" p-id="5738"></path></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1545064197737" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14227" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M759.725253 0H134.464646v1024h817.131314V191.870707z" fill="#02007A" p-id="14228"></path><path d="M72.40404 708.525253h687.838384v258.585858H72.40404z" fill="#230091" p-id="14229"></path><path d="M82.747475 718.868687h667.151515v237.89899H82.747475zM755.070707 5.171717l191.353535 191.353536h-191.353535z" fill="#FFFFFF" p-id="14230"></path><path d="M506.828283 791.272727v93.090909h25.858586c28.444444 0 41.373737-12.929293 41.373737-45.511111s-12.929293-47.579798-41.373737-47.579798h-25.858586z m-25.858586-25.858586h51.717172c42.408081 0 67.232323 29.99596 67.232323 74.989899S575.612121 910.222222 532.686869 910.222222H480.969697v-144.808081z m-132.39596 104.468687c1.034343 26.375758 22.755556 42.408081 55.337374 42.408081 34.650505 0 55.854545-17.066667 55.854546-43.959596 0-21.20404-12.412121-33.09899-40.856566-39.822222l-16.549495-3.620202c-17.583838-4.137374-24.824242-9.826263-24.824242-19.135354 0-11.894949 10.860606-19.652525 26.892929-19.652525s27.410101 7.757576 28.444444 21.204041h24.307071c-0.517172-24.824242-21.20404-41.890909-52.751515-41.89091S351.676768 782.480808 351.676768 807.822222c0 20.169697 12.412121 33.09899 38.787878 38.787879l18.618182 4.137374c18.10101 4.137374 25.341414 10.343434 25.341414 20.169697 0 11.894949-11.894949 20.169697-28.961616 20.169697s-30.513131-8.791919-32.064646-21.721212l-24.824243 0.517171zM258.585859 788.686869v53.785858h20.686868c18.10101 0 31.030303-9.826263 31.030303-26.892929s-12.412121-26.892929-31.030303-26.892929h-20.686868z m-25.858586-23.272728h56.888889c28.444444 0 46.545455 20.686869 46.545454 49.131314s-17.583838 49.131313-46.545454 49.131313h-31.030303v46.545454h-25.858586v-144.808081z" fill="#230091" p-id="14231"></path><path d="M415.806061 443.216162h-31.030303v137.567676H321.163636V196.525253h93.090909c91.022222 0 140.670707 35.684848 140.670708 122.052525 0.517172 103.434343-92.573737 124.638384-139.119192 124.638384z m-5.688889-192.905051c-6.206061 0-14.99798 0.517172-26.89293 1.551515V387.878788h36.202021C461.834343 387.878788 491.313131 366.157576 491.313131 312.888889c0-40.856566-32.064646-62.577778-81.195959-62.577778z m258.068687 337.19596c70.852525 0 96.711111-50.165657 96.711111-84.816162 0-54.820202-35.684848-67.749495-66.19798-87.40202-25.858586-17.066667-49.648485-23.272727-49.648485-40.856566 0-22.755556 15.515152-27.410101 36.20202-27.410101 25.858586 0 54.820202 2.068687 67.232323 16.032323V306.682828c-12.929293-10.343434-37.236364-13.446465-68.783838-13.446464-67.749495 0-93.608081 44.993939-93.608081 84.29899 0 50.682828 33.616162 65.680808 66.19798 83.781818 21.20404 11.894949 46.545455 20.686869 46.545455 44.476767 0 13.963636-8.791919 27.927273-37.753536 27.927273-26.892929 0-59.991919-10.343434-74.989899-23.789899v59.991919c13.446465 10.343434 40.339394 17.583838 78.09293 17.583839z" fill="#7AB7FF" p-id="14232"></path></svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1545064197737" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="14227" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M759.725253 0H134.464646v1024h817.131314V191.870707z" fill="#02007A" p-id="14228"></path>
|
||||
<path d="M72.40404 708.525253h687.838384v258.585858H72.40404z" fill="#230091" p-id="14229"></path>
|
||||
<path d="M82.747475 718.868687h667.151515v237.89899H82.747475zM755.070707 5.171717l191.353535 191.353536h-191.353535z"
|
||||
fill="#FFFFFF" p-id="14230"></path>
|
||||
<path d="M506.828283 791.272727v93.090909h25.858586c28.444444 0 41.373737-12.929293 41.373737-45.511111s-12.929293-47.579798-41.373737-47.579798h-25.858586z m-25.858586-25.858586h51.717172c42.408081 0 67.232323 29.99596 67.232323 74.989899S575.612121 910.222222 532.686869 910.222222H480.969697v-144.808081z m-132.39596 104.468687c1.034343 26.375758 22.755556 42.408081 55.337374 42.408081 34.650505 0 55.854545-17.066667 55.854546-43.959596 0-21.20404-12.412121-33.09899-40.856566-39.822222l-16.549495-3.620202c-17.583838-4.137374-24.824242-9.826263-24.824242-19.135354 0-11.894949 10.860606-19.652525 26.892929-19.652525s27.410101 7.757576 28.444444 21.204041h24.307071c-0.517172-24.824242-21.20404-41.890909-52.751515-41.89091S351.676768 782.480808 351.676768 807.822222c0 20.169697 12.412121 33.09899 38.787878 38.787879l18.618182 4.137374c18.10101 4.137374 25.341414 10.343434 25.341414 20.169697 0 11.894949-11.894949 20.169697-28.961616 20.169697s-30.513131-8.791919-32.064646-21.721212l-24.824243 0.517171zM258.585859 788.686869v53.785858h20.686868c18.10101 0 31.030303-9.826263 31.030303-26.892929s-12.412121-26.892929-31.030303-26.892929h-20.686868z m-25.858586-23.272728h56.888889c28.444444 0 46.545455 20.686869 46.545454 49.131314s-17.583838 49.131313-46.545454 49.131313h-31.030303v46.545454h-25.858586v-144.808081z"
|
||||
fill="#230091" p-id="14231"></path>
|
||||
<path d="M415.806061 443.216162h-31.030303v137.567676H321.163636V196.525253h93.090909c91.022222 0 140.670707 35.684848 140.670708 122.052525 0.517172 103.434343-92.573737 124.638384-139.119192 124.638384z m-5.688889-192.905051c-6.206061 0-14.99798 0.517172-26.89293 1.551515V387.878788h36.202021C461.834343 387.878788 491.313131 366.157576 491.313131 312.888889c0-40.856566-32.064646-62.577778-81.195959-62.577778z m258.068687 337.19596c70.852525 0 96.711111-50.165657 96.711111-84.816162 0-54.820202-35.684848-67.749495-66.19798-87.40202-25.858586-17.066667-49.648485-23.272727-49.648485-40.856566 0-22.755556 15.515152-27.410101 36.20202-27.410101 25.858586 0 54.820202 2.068687 67.232323 16.032323V306.682828c-12.929293-10.343434-37.236364-13.446465-68.783838-13.446464-67.749495 0-93.608081 44.993939-93.608081 84.29899 0 50.682828 33.616162 65.680808 66.19798 83.781818 21.20404 11.894949 46.545455 20.686869 46.545455 44.476767 0 13.963636-8.791919 27.927273-37.753536 27.927273-26.892929 0-59.991919-10.343434-74.989899-23.789899v59.991919c13.446465 10.343434 40.339394 17.583838 78.09293 17.583839z"
|
||||
fill="#7AB7FF" p-id="14232"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1514189544355" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8186" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M176 1024l672 0c26.464 0 48-21.536 48-48l0-672c0-0.544-0.256-0.992-0.288-1.504-0.064-0.736-0.256-1.376-0.416-2.08-0.544-2.272-1.472-4.32-2.88-6.112-0.224-0.288-0.192-0.64-0.416-0.928l-256-288c-0.096-0.096-0.224-0.096-0.32-0.192-1.92-2.048-4.352-3.456-7.136-4.288-0.608-0.192-1.152-0.256-1.792-0.352-0.928-0.16-1.792-0.544-2.752-0.544l-448 0c-26.464 0-48 21.536-48 48l0 928c0 26.464 21.536 48 48 48zM640 58.08l204.384 229.92-188.384 0c-7.04 0-16-13.44-16-24l0-205.92zM160 48c0-8.832 7.2-16 16-16l432 0 0 232c0 25.888 20.96 56 48 56l208 0 0 656c0 8.832-7.2 16-16 16l-672 0c-8.96 0-16-7.04-16-16l0-928zM304 448l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 576l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 320l192 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-192 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 704l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 832l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16z" p-id="8187"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1514189544355" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="8186" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M176 1024l672 0c26.464 0 48-21.536 48-48l0-672c0-0.544-0.256-0.992-0.288-1.504-0.064-0.736-0.256-1.376-0.416-2.08-0.544-2.272-1.472-4.32-2.88-6.112-0.224-0.288-0.192-0.64-0.416-0.928l-256-288c-0.096-0.096-0.224-0.096-0.32-0.192-1.92-2.048-4.352-3.456-7.136-4.288-0.608-0.192-1.152-0.256-1.792-0.352-0.928-0.16-1.792-0.544-2.752-0.544l-448 0c-26.464 0-48 21.536-48 48l0 928c0 26.464 21.536 48 48 48zM640 58.08l204.384 229.92-188.384 0c-7.04 0-16-13.44-16-24l0-205.92zM160 48c0-8.832 7.2-16 16-16l432 0 0 232c0 25.888 20.96 56 48 56l208 0 0 656c0 8.832-7.2 16-16 16l-672 0c-8.96 0-16-7.04-16-16l0-928zM304 448l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 576l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 320l192 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-192 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 704l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16zM304 832l416 0c8.832 0 16-7.168 16-16s-7.168-16-16-16l-416 0c-8.832 0-16 7.168-16 16s7.168 16 16 16z"
|
||||
p-id="8187"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1514189554639" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="8871" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M80 34.4h864v960H80z" fill="#8095FF" p-id="8872"></path>
|
||||
<path d="M176 112m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8873"></path>
|
||||
<path d="M176 272m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8874"></path>
|
||||
<path d="M176 432m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8875"></path>
|
||||
<path d="M176 592m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8876"></path>
|
||||
<path d="M176 752m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8877"></path>
|
||||
<path d="M176 912m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8878"></path>
|
||||
<path d="M864 112m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8879"></path>
|
||||
<path d="M864 272m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8880"></path>
|
||||
<path d="M864 432m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8881"></path>
|
||||
<path d="M864 592m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8882"></path>
|
||||
<path d="M864 752m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8883"></path>
|
||||
<path d="M864 912m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8884"></path>
|
||||
<path d="M648 508L436 362.4c-4.8-3.2-11.2-4-16.8-0.8-5.6 3.2-8.8 8.8-8.8 14.4v290.4c0 5.6 3.2 11.2 8.8 14.4 5.6 3.2 12 2.4 16.8-0.8L648 533.6c4.8-2.4 7.2-8 7.2-12.8 0-4.8-3.2-9.6-7.2-12.8z"
|
||||
fill="#FFFFFF" p-id="8885"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1514189554639" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8871" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M80 34.4h864v960H80z" fill="#8095FF" p-id="8872"></path><path d="M176 112m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8873"></path><path d="M176 272m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8874"></path><path d="M176 432m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8875"></path><path d="M176 592m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8876"></path><path d="M176 752m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8877"></path><path d="M176 912m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8878"></path><path d="M864 112m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8879"></path><path d="M864 272m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8880"></path><path d="M864 432m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8881"></path><path d="M864 592m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8882"></path><path d="M864 752m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8883"></path><path d="M864 912m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" fill="#FFFFFF" p-id="8884"></path><path d="M648 508L436 362.4c-4.8-3.2-11.2-4-16.8-0.8-5.6 3.2-8.8 8.8-8.8 14.4v290.4c0 5.6 3.2 11.2 8.8 14.4 5.6 3.2 12 2.4 16.8-0.8L648 533.6c4.8-2.4 7.2-8 7.2-12.8 0-4.8-3.2-9.6-7.2-12.8z" fill="#FFFFFF" p-id="8885"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1543300142282" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="2233" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path d="M534.310676 38.36489h65.428495v88.119185c110.25913 0 220.507245 0.220298 330.766374-0.330447 18.615178 0.771043 39.113903-0.550745 55.305803 10.574302 11.345345 16.302049 10.023557 37.010058 10.783586 55.746399-0.550745 191.218631-0.319432 382.327112-0.220298 573.435594-0.550745 32.053353 2.974022 64.767601-3.745066 96.380358-4.405959 22.910988-31.943204 23.461733-50.338084 24.232776-114.114344 0.330447-228.338837-0.220298-342.56333 0v99.134083h-68.402517c-167.867047-30.511268-336.075555-58.709407-504.151885-88.119185v-770.932716c169.0897-29.420793 338.168386-58.389975 507.136922-88.240349z"
|
||||
fill="#207245" p-id="2234"></path>
|
||||
<path d="M599.739171 159.528769h363.491636v693.938579h-363.491636v-66.089389h88.119184v-77.104286h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-66.089388z"
|
||||
fill="#FFFFFF" p-id="2235"></path>
|
||||
<path d="M731.917948 225.618157h154.208573v77.104287h-154.208573v-77.104287z" fill="#207245" p-id="2236"></path>
|
||||
<path d="M353.44605 328.827752c24.89367-1.762384 49.897488-3.304469 74.901307-4.516108-29.409778 60.251492-58.929705 120.502985-88.879213 180.53418 30.29097 61.683429 61.242833 122.926263 91.632937 184.609692-26.545904-1.553101-52.98166-3.19432-79.527564-5.066854-18.725327-45.932125-41.526166-90.322164-54.964342-138.358134-14.969246 44.731501-36.349164 86.918561-53.521389 130.74684-24.122627-0.330447-48.256268-1.321788-72.367881-2.313129 28.308288-55.404937 55.625235-111.250471 84.814716-166.324961-24.783521-56.726725-52.001334-112.35196-77.544883-168.748238 24.232776-1.431937 48.454537-2.863874 72.698327-4.185662 16.412198 43.068251 34.366482 85.585758 47.925822 129.7555 14.506621-46.802302 36.216985-90.861894 54.832163-136.133126z"
|
||||
fill="#FFFFFF" p-id="2237"></path>
|
||||
<path d="M731.917948 346.782036h154.208573v77.104287h-154.208573v-77.104287zM731.917948 467.945915h154.208573v77.104287h-154.208573v-77.104287zM731.917948 589.109794h154.208573v77.104287h-154.208573v-77.104287zM731.917948 710.273673h154.208573v77.104286h-154.208573v-77.104286z"
|
||||
fill="#207245" p-id="2238"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543300142282" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2233" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M534.310676 38.36489h65.428495v88.119185c110.25913 0 220.507245 0.220298 330.766374-0.330447 18.615178 0.771043 39.113903-0.550745 55.305803 10.574302 11.345345 16.302049 10.023557 37.010058 10.783586 55.746399-0.550745 191.218631-0.319432 382.327112-0.220298 573.435594-0.550745 32.053353 2.974022 64.767601-3.745066 96.380358-4.405959 22.910988-31.943204 23.461733-50.338084 24.232776-114.114344 0.330447-228.338837-0.220298-342.56333 0v99.134083h-68.402517c-167.867047-30.511268-336.075555-58.709407-504.151885-88.119185v-770.932716c169.0897-29.420793 338.168386-58.389975 507.136922-88.240349z" fill="#207245" p-id="2234"></path><path d="M599.739171 159.528769h363.491636v693.938579h-363.491636v-66.089389h88.119184v-77.104286h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-44.059592h88.119184v-77.104287h-88.119184v-66.089388z" fill="#FFFFFF" p-id="2235"></path><path d="M731.917948 225.618157h154.208573v77.104287h-154.208573v-77.104287z" fill="#207245" p-id="2236"></path><path d="M353.44605 328.827752c24.89367-1.762384 49.897488-3.304469 74.901307-4.516108-29.409778 60.251492-58.929705 120.502985-88.879213 180.53418 30.29097 61.683429 61.242833 122.926263 91.632937 184.609692-26.545904-1.553101-52.98166-3.19432-79.527564-5.066854-18.725327-45.932125-41.526166-90.322164-54.964342-138.358134-14.969246 44.731501-36.349164 86.918561-53.521389 130.74684-24.122627-0.330447-48.256268-1.321788-72.367881-2.313129 28.308288-55.404937 55.625235-111.250471 84.814716-166.324961-24.783521-56.726725-52.001334-112.35196-77.544883-168.748238 24.232776-1.431937 48.454537-2.863874 72.698327-4.185662 16.412198 43.068251 34.366482 85.585758 47.925822 129.7555 14.506621-46.802302 36.216985-90.861894 54.832163-136.133126z" fill="#FFFFFF" p-id="2237"></path><path d="M731.917948 346.782036h154.208573v77.104287h-154.208573v-77.104287zM731.917948 467.945915h154.208573v77.104287h-154.208573v-77.104287zM731.917948 589.109794h154.208573v77.104287h-154.208573v-77.104287zM731.917948 710.273673h154.208573v77.104286h-154.208573v-77.104286z" fill="#207245" p-id="2238"></path></svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -11,10 +11,10 @@
|
||||
@REM prepare the variables.
|
||||
|
||||
@REM version name
|
||||
SET VERSION_NAME=tank-3.1.2
|
||||
SET VERSION_NAME=tank-4.2.0
|
||||
ECHO VERSION_NAME: %VERSION_NAME%
|
||||
@REM golang proxy
|
||||
SET GOPROXY=https://athens.azurefd.net
|
||||
SET GOPROXY=https://goproxy.cn
|
||||
ECHO GOPROXY: %GOPROXY%
|
||||
@REM assign variable like Linux GOARCH=$(go env GOARCH) eg. amd64
|
||||
FOR /f %%i IN ('go env GOARCH') DO SET GOARCH=%%i
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
@@ -9,10 +10,10 @@
|
||||
#prepare the variables.
|
||||
|
||||
# version name
|
||||
VERSION_NAME=tank-3.1.2
|
||||
VERSION_NAME=tank-4.2.0
|
||||
echo "VERSION_NAME: ${VERSION_NAME}"
|
||||
# golang proxy
|
||||
GOPROXY=https://athens.azurefd.net
|
||||
GOPROXY=https://goproxy.cn
|
||||
echo "GOPROXY: ${GOPROXY}"
|
||||
# eg. amd64
|
||||
GOARCH=$(go env GOARCH)
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
#!/bin/bash
|
||||
#set -e
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
# Tank cross-compilation build script
|
||||
# Supports building for multiple platforms and architectures
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print usage information
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -v, --version VERSION Version name (default: tank-4.2.0)"
|
||||
echo " -o, --os OS Target OS: linux, windows, darwin, or 'all'"
|
||||
echo " -a, --arch ARCH Target architecture: amd64, arm64, 386, or 'all'"
|
||||
echo " -p, --platform PLATFORM Specific platform (OS/ARCH format, e.g. linux/amd64)"
|
||||
echo " --list-platforms List all supported platforms"
|
||||
echo " --clean Clean dist directory before building"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Build for current platform"
|
||||
echo " $0 -o linux -a amd64 # Build for Linux AMD64"
|
||||
echo " $0 -p linux/arm64 # Build for Linux ARM64"
|
||||
echo " $0 -o all -a amd64 # Build for all OS with AMD64"
|
||||
echo " $0 -o all -a all # Build for all supported platforms"
|
||||
echo " $0 --clean -o all -a all # Clean and build all platforms"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# List supported platforms
|
||||
list_platforms() {
|
||||
echo "Supported platforms:"
|
||||
echo " linux/amd64 - Linux 64-bit"
|
||||
echo " linux/arm64 - Linux ARM64"
|
||||
echo " linux/386 - Linux 32-bit"
|
||||
echo " windows/amd64 - Windows 64-bit"
|
||||
echo " windows/arm64 - Windows ARM64"
|
||||
echo " windows/386 - Windows 32-bit"
|
||||
echo " darwin/amd64 - macOS Intel"
|
||||
echo " darwin/arm64 - macOS Apple Silicon"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Log functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Default values
|
||||
VERSION_NAME="tank-4.2.0"
|
||||
TARGET_OS=""
|
||||
TARGET_ARCH=""
|
||||
SPECIFIC_PLATFORM=""
|
||||
BUILD_ALL_OS=false
|
||||
BUILD_ALL_ARCH=false
|
||||
CLEAN_DIST=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-v|--version)
|
||||
VERSION_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
-o|--os)
|
||||
if [[ "$2" == "all" ]]; then
|
||||
BUILD_ALL_OS=true
|
||||
else
|
||||
TARGET_OS="$2"
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
-a|--arch)
|
||||
if [[ "$2" == "all" ]]; then
|
||||
BUILD_ALL_ARCH=true
|
||||
else
|
||||
TARGET_ARCH="$2"
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
-p|--platform)
|
||||
SPECIFIC_PLATFORM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--list-platforms)
|
||||
list_platforms
|
||||
;;
|
||||
--clean)
|
||||
CLEAN_DIST=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Parse specific platform
|
||||
if [[ -n "$SPECIFIC_PLATFORM" ]]; then
|
||||
if [[ "$SPECIFIC_PLATFORM" =~ ^([^/]+)/([^/]+)$ ]]; then
|
||||
TARGET_OS="${BASH_REMATCH[1]}"
|
||||
TARGET_ARCH="${BASH_REMATCH[2]}"
|
||||
else
|
||||
log_error "Invalid platform format. Use OS/ARCH format (e.g., linux/amd64)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set defaults if nothing specified
|
||||
if [[ -z "$TARGET_OS" && "$BUILD_ALL_OS" == false ]]; then
|
||||
TARGET_OS=$(go env GOOS)
|
||||
fi
|
||||
|
||||
if [[ -z "$TARGET_ARCH" && "$BUILD_ALL_ARCH" == false ]]; then
|
||||
TARGET_ARCH=$(go env GOARCH)
|
||||
fi
|
||||
|
||||
# Define supported platforms as a simple list
|
||||
SUPPORTED_PLATFORMS="linux/amd64 linux/arm64 linux/386 windows/amd64 windows/arm64 windows/386 darwin/amd64 darwin/arm64"
|
||||
|
||||
# Check if platform is supported
|
||||
is_platform_supported() {
|
||||
local platform="$1"
|
||||
echo "$SUPPORTED_PLATFORMS" | grep -q "\b$platform\b"
|
||||
}
|
||||
|
||||
# Validate platform
|
||||
validate_platform() {
|
||||
local os=$1
|
||||
local arch=$2
|
||||
local platform="${os}/${arch}"
|
||||
|
||||
if ! is_platform_supported "$platform"; then
|
||||
log_warn "Platform ${platform} may not be fully tested"
|
||||
return 1
|
||||
fi
|
||||
log_info "Platform ${platform} supported"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Setup directories
|
||||
GOPROXY=https://goproxy.cn
|
||||
PACK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
BUILD_DIR=$(dirname ${PACK_DIR})
|
||||
PROJECT_DIR=$(dirname ${BUILD_DIR})
|
||||
DIST_DIR=${PROJECT_DIR}/tmp/dist
|
||||
|
||||
log_info "=== Tank Cross-Compilation Build ==="
|
||||
log_info "Version: ${VERSION_NAME}"
|
||||
log_info "GOPROXY: ${GOPROXY}"
|
||||
log_info "Project: ${PROJECT_DIR}"
|
||||
log_info "Dist: ${DIST_DIR}"
|
||||
|
||||
# Clean dist directory if requested
|
||||
if [[ "$CLEAN_DIST" == true ]]; then
|
||||
log_info "Cleaning dist directory..."
|
||||
rm -rf ${DIST_DIR}
|
||||
fi
|
||||
|
||||
# Create dist directory
|
||||
mkdir -p ${DIST_DIR}
|
||||
|
||||
# Build function for specific platform
|
||||
build_platform() {
|
||||
local os=$1
|
||||
local arch=$2
|
||||
local platform="${os}/${arch}"
|
||||
|
||||
log_info "Building ${platform}..."
|
||||
|
||||
# Validate platform
|
||||
validate_platform "$os" "$arch"
|
||||
|
||||
# Determine binary name and archive format
|
||||
local binary_name="tank"
|
||||
local archive_ext="tar.gz"
|
||||
if [[ "$os" == "windows" ]]; then
|
||||
binary_name="tank.exe"
|
||||
archive_ext="zip"
|
||||
fi
|
||||
|
||||
# Setup paths
|
||||
local file_name="${VERSION_NAME}.${os}-${arch}.${archive_ext}"
|
||||
local component_dir="${DIST_DIR}/${VERSION_NAME}-${os}-${arch}"
|
||||
local dist_path="${DIST_DIR}/${file_name}"
|
||||
|
||||
log_info " Target: ${platform}"
|
||||
log_info " Binary: ${binary_name}"
|
||||
log_info " Output: ${file_name}"
|
||||
|
||||
# Clean previous build
|
||||
if [[ -d "$component_dir" ]]; then
|
||||
rm -rf "$component_dir"
|
||||
fi
|
||||
mkdir -p "$component_dir"
|
||||
|
||||
# Change to project directory
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
# Cross-compile
|
||||
log_info " Cross-compiling..."
|
||||
export GOOS="$os"
|
||||
export GOARCH="$arch"
|
||||
export GOPROXY="$GOPROXY"
|
||||
export CGO_ENABLED=0 # Disable CGO for cross-compilation
|
||||
|
||||
log_info " Starting go build..." # 添加此行
|
||||
if ! go build -mod=readonly -ldflags="-s -w" -o "${binary_name}"; then
|
||||
log_error "Failed to build for ${platform}"
|
||||
return 1
|
||||
fi
|
||||
log_info " go build completed" # 添加此行
|
||||
|
||||
# Copy binary
|
||||
log_info " Copying binary..."
|
||||
cp "./${binary_name}" "${component_dir}/"
|
||||
|
||||
# Copy configuration and static files
|
||||
log_info " Copying build files..."
|
||||
if [[ -d "${BUILD_DIR}/conf" ]]; then
|
||||
cp -r "${BUILD_DIR}/conf" "${component_dir}/"
|
||||
fi
|
||||
if [[ -d "${BUILD_DIR}/html" ]]; then
|
||||
cp -r "${BUILD_DIR}/html" "${component_dir}/"
|
||||
fi
|
||||
if [[ -d "${BUILD_DIR}/service" ]]; then
|
||||
cp -r "${BUILD_DIR}/service" "${component_dir}/"
|
||||
fi
|
||||
|
||||
# Create archive
|
||||
log_info " Creating archive..."
|
||||
cd ${DIST_DIR}
|
||||
|
||||
if [[ "$archive_ext" == "zip" ]]; then
|
||||
# Use zip for Windows
|
||||
if command -v zip >/dev/null 2>&1; then
|
||||
zip -r "${file_name}" "$(basename "$component_dir")" >/dev/null 2>&1
|
||||
else
|
||||
log_warn "zip command not found, using tar.gz instead"
|
||||
tar -zcf "${VERSION_NAME}.${os}-${arch}.tar.gz" "$(basename "$component_dir")" >/dev/null 2>&1
|
||||
fi
|
||||
else
|
||||
# Use tar.gz for Unix-like systems
|
||||
tar -zcf "${file_name}" "$(basename "$component_dir")" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log_error "Failed to create archive for ${platform}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
cd ${PROJECT_DIR}
|
||||
rm -f "./${binary_name}"
|
||||
rm -rf "$component_dir"
|
||||
|
||||
# Get file size
|
||||
local file_size=$(ls -lh "${DIST_DIR}/${file_name}" | awk '{print $5}')
|
||||
log_success "Built ${file_name} (${file_size})"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Build execution
|
||||
build_count=0
|
||||
success_count=0
|
||||
failed_builds=()
|
||||
|
||||
if [[ "$BUILD_ALL_OS" == true && "$BUILD_ALL_ARCH" == true ]]; then
|
||||
log_info "Building for all supported platforms..."
|
||||
# Build all supported platforms explicitly
|
||||
for os in linux windows darwin; do
|
||||
for arch in amd64 arm64 386; do
|
||||
platform="${os}/${arch}"
|
||||
if is_platform_supported "$platform"; then
|
||||
((build_count++))
|
||||
if build_platform "$os" "$arch"; then
|
||||
((success_count++))
|
||||
else
|
||||
failed_builds+=("$platform")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
elif [[ "$BUILD_ALL_OS" == true ]]; then
|
||||
log_info "Building for all OS with architecture: ${TARGET_ARCH}"
|
||||
for os in linux windows darwin; do
|
||||
platform="${os}/${TARGET_ARCH}"
|
||||
if is_platform_supported "$platform"; then
|
||||
((build_count++))
|
||||
if build_platform "$os" "$TARGET_ARCH"; then
|
||||
((success_count++))
|
||||
else
|
||||
failed_builds+=("$platform")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
elif [[ "$BUILD_ALL_ARCH" == true ]]; then
|
||||
log_info "Building for OS ${TARGET_OS} with all architectures"
|
||||
for arch in amd64 arm64 386; do
|
||||
platform="${TARGET_OS}/${arch}"
|
||||
if is_platform_supported "$platform"; then
|
||||
((build_count++))
|
||||
if build_platform "$TARGET_OS" "$arch"; then
|
||||
((success_count++))
|
||||
else
|
||||
failed_builds+=("$platform")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
log_info "Building for ${TARGET_OS}/${TARGET_ARCH}"
|
||||
((build_count++))
|
||||
if build_platform "$TARGET_OS" "$TARGET_ARCH"; then
|
||||
((success_count++))
|
||||
else
|
||||
failed_builds+=("${TARGET_OS}/${TARGET_ARCH}")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build summary
|
||||
echo ""
|
||||
log_info "=== Build Summary ==="
|
||||
echo "Total builds: ${build_count}"
|
||||
echo "Successful: ${success_count}"
|
||||
echo "Failed: $((build_count - success_count))"
|
||||
|
||||
if [[ ${#failed_builds[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
log_warn "Failed builds:"
|
||||
for platform in "${failed_builds[@]}"; do
|
||||
echo " - $platform"
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $success_count -gt 0 ]]; then
|
||||
echo ""
|
||||
log_success "Generated files:"
|
||||
ls -lh ${DIST_DIR}/*.{tar.gz,zip} 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
|
||||
|
||||
echo ""
|
||||
log_info "Files located in: ${DIST_DIR}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ $((build_count - success_count)) -eq 0 ]]; then
|
||||
log_success "All builds completed successfully!"
|
||||
exit 0
|
||||
else
|
||||
log_error "Some builds failed!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,5 +1,7 @@
|
||||
package core
|
||||
|
||||
import "gorm.io/gorm/schema"
|
||||
|
||||
const (
|
||||
//authentication key of cookie
|
||||
COOKIE_AUTH_KEY = "_ak"
|
||||
@@ -9,19 +11,25 @@ const (
|
||||
|
||||
DEFAULT_SERVER_PORT = 6010
|
||||
|
||||
//db table's prefix. tank31_ means current version is tank:3.1.x
|
||||
TABLE_PREFIX = "tank31_"
|
||||
//db table's prefix. tank42_ means current version is tank:4.2.x
|
||||
TABLE_PREFIX = "tank42_"
|
||||
|
||||
VERSION = "3.1.2"
|
||||
VERSION = "4.2.0"
|
||||
)
|
||||
|
||||
type Config interface {
|
||||
Installed() bool
|
||||
ServerPort() int
|
||||
//get the db type
|
||||
DbType() string
|
||||
//get the mysql url. eg. tank:tank123@tcp(127.0.0.1:3306)/tank?charset=utf8&parseTime=True&loc=Local
|
||||
MysqlUrl() string
|
||||
//get the sqlite path
|
||||
SqliteFolder() string
|
||||
//files storage location.
|
||||
MatterPath() string
|
||||
//table name strategy
|
||||
NamingStrategy() schema.NamingStrategy
|
||||
//when installed by user. Write configs to tank.json
|
||||
FinishInstall(mysqlPort int, mysqlHost string, mysqlSchema string, mysqlUsername string, mysqlPassword string, mysqlCharset string)
|
||||
FinishInstall(dbType string, mysqlPort int, mysqlHost string, mysqlSchema string, mysqlUsername string, mysqlPassword string, mysqlCharset string)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/tool/cache"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ import (
|
||||
|
||||
type AlienController struct {
|
||||
BaseController
|
||||
uploadTokenDao *UploadTokenDao
|
||||
downloadTokenDao *DownloadTokenDao
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
imageCacheDao *ImageCacheDao
|
||||
imageCacheService *ImageCacheService
|
||||
alienService *AlienService
|
||||
shareService *ShareService
|
||||
uploadTokenDao *UploadTokenDao
|
||||
downloadTokenDao *DownloadTokenDao
|
||||
matterDao *MatterDao
|
||||
spaceDao *SpaceDao
|
||||
matterService *MatterService
|
||||
imageCacheDao *ImageCacheDao
|
||||
imageCacheService *ImageCacheService
|
||||
alienService *AlienService
|
||||
shareService *ShareService
|
||||
spaceMemberService *SpaceMemberService
|
||||
}
|
||||
|
||||
func (this *AlienController) Init() {
|
||||
@@ -40,6 +42,11 @@ func (this *AlienController) Init() {
|
||||
this.matterDao = c
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceDao)
|
||||
if c, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = c
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterService)
|
||||
if c, ok := b.(*MatterService); ok {
|
||||
this.matterService = c
|
||||
@@ -64,6 +71,11 @@ func (this *AlienController) Init() {
|
||||
if c, ok := b.(*ShareService); ok {
|
||||
this.shareService = c
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceMemberService)
|
||||
if b, ok := b.(*SpaceMemberService); ok {
|
||||
this.spaceMemberService = b
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AlienController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
|
||||
@@ -80,7 +92,7 @@ func (this *AlienController) RegisterRoutes() map[string]func(writer http.Respon
|
||||
return routeMap
|
||||
}
|
||||
|
||||
//handle some special routes, eg. params in the url.
|
||||
// handle some special routes, eg. params in the url.
|
||||
func (this *AlienController) HandleRoutes(writer http.ResponseWriter, request *http.Request) (func(writer http.ResponseWriter, request *http.Request), bool) {
|
||||
|
||||
path := request.URL.Path
|
||||
@@ -108,7 +120,7 @@ func (this *AlienController) HandleRoutes(writer http.ResponseWriter, request *h
|
||||
return nil, false
|
||||
}
|
||||
|
||||
//fetch a upload token for guest. Guest can upload file with this token.
|
||||
// fetch a upload token for guest. Guest can upload file with this token.
|
||||
func (this *AlienController) FetchUploadToken(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
filename := request.FormValue("filename")
|
||||
@@ -151,7 +163,8 @@ func (this *AlienController) FetchUploadToken(writer http.ResponseWriter, reques
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
dirMatter := this.matterService.CreateDirectories(request, user, dirPath)
|
||||
space := this.spaceDao.CheckByUuid(user.SpaceUuid)
|
||||
dirMatter := this.matterService.CreateDirectories(request, user, space, dirPath)
|
||||
|
||||
uploadToken := &UploadToken{
|
||||
UserUuid: user.Uuid,
|
||||
@@ -170,7 +183,7 @@ func (this *AlienController) FetchUploadToken(writer http.ResponseWriter, reques
|
||||
|
||||
}
|
||||
|
||||
//user confirm a file whether uploaded successfully.
|
||||
// user confirm a file whether uploaded successfully.
|
||||
func (this *AlienController) Confirm(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
matterUuid := request.FormValue("matterUuid")
|
||||
@@ -188,7 +201,7 @@ func (this *AlienController) Confirm(writer http.ResponseWriter, request *http.R
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
//a guest upload a file with a upload token.
|
||||
// a guest upload a file with a upload token.
|
||||
func (this *AlienController) Upload(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
//allow cors.
|
||||
this.allowCORS(writer)
|
||||
@@ -216,6 +229,7 @@ func (this *AlienController) Upload(writer http.ResponseWriter, request *http.Re
|
||||
}
|
||||
|
||||
user := this.userDao.CheckByUuid(uploadToken.UserUuid)
|
||||
space := this.spaceDao.CheckByUuid(user.SpaceUuid)
|
||||
|
||||
err = request.ParseMultipartForm(32 << 20)
|
||||
this.PanicError(err)
|
||||
@@ -228,9 +242,9 @@ func (this *AlienController) Upload(writer http.ResponseWriter, request *http.Re
|
||||
panic(result.BadRequest("file size doesn't the one in uploadToken"))
|
||||
}
|
||||
|
||||
dirMatter := this.matterDao.CheckWithRootByUuid(uploadToken.FolderUuid, user)
|
||||
dirMatter := this.matterDao.CheckWithRootByUuid(uploadToken.FolderUuid, space)
|
||||
|
||||
matter := this.matterService.Upload(request, file, user, dirMatter, uploadToken.Filename, uploadToken.Privacy)
|
||||
matter := this.matterService.Upload(request, file, handler, user, space, dirMatter, uploadToken.Filename, uploadToken.Privacy)
|
||||
|
||||
//expire the upload token.
|
||||
uploadToken.ExpireTime = time.Now()
|
||||
@@ -239,7 +253,7 @@ func (this *AlienController) Upload(writer http.ResponseWriter, request *http.Re
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
//crawl a url with uploadToken. guest can visit this method.
|
||||
// crawl a url with uploadToken. guest can visit this method.
|
||||
func (this *AlienController) CrawlToken(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
//allow cors.
|
||||
@@ -263,10 +277,11 @@ func (this *AlienController) CrawlToken(writer http.ResponseWriter, request *htt
|
||||
}
|
||||
|
||||
user := this.userDao.CheckByUuid(uploadToken.UserUuid)
|
||||
space := this.spaceDao.CheckByUuid(user.SpaceUuid)
|
||||
|
||||
dirMatter := this.matterDao.CheckWithRootByUuid(uploadToken.FolderUuid, user)
|
||||
dirMatter := this.matterDao.CheckWithRootByUuid(uploadToken.FolderUuid, space)
|
||||
|
||||
matter := this.matterService.AtomicCrawl(request, url, uploadToken.Filename, user, dirMatter, uploadToken.Privacy)
|
||||
matter := this.matterService.AtomicCrawl(request, url, uploadToken.Filename, user, space, dirMatter, uploadToken.Privacy)
|
||||
|
||||
//expire the upload token.
|
||||
uploadToken.ExpireTime = time.Now()
|
||||
@@ -275,7 +290,7 @@ func (this *AlienController) CrawlToken(writer http.ResponseWriter, request *htt
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
//crawl a url directly. only user can visit this method.
|
||||
// crawl a url directly. only user can visit this method.
|
||||
func (this *AlienController) CrawlDirect(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
filename := request.FormValue("filename")
|
||||
@@ -291,14 +306,15 @@ func (this *AlienController) CrawlDirect(writer http.ResponseWriter, request *ht
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
dirMatter := this.matterService.CreateDirectories(request, user, dirPath)
|
||||
space := this.spaceDao.CheckByUuid(user.SpaceUuid)
|
||||
dirMatter := this.matterService.CreateDirectories(request, user, space, dirPath)
|
||||
|
||||
matter := this.matterService.AtomicCrawl(request, url, filename, user, dirMatter, privacy)
|
||||
matter := this.matterService.AtomicCrawl(request, url, filename, user, space, dirMatter, privacy)
|
||||
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
//fetch a download token for guest. Guest can download file with this token.
|
||||
// fetch a download token for guest. Guest can download file with this token.
|
||||
func (this *AlienController) FetchDownloadToken(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
matterUuid := request.FormValue("matterUuid")
|
||||
@@ -311,8 +327,9 @@ func (this *AlienController) FetchDownloadToken(writer http.ResponseWriter, requ
|
||||
user := this.checkUser(request)
|
||||
|
||||
matter := this.matterDao.CheckByUuid(matterUuid)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
panic(result.BadRequest("matter not belong to you"))
|
||||
canRead := this.spaceMemberService.canRead(user, matter.SpaceUuid)
|
||||
if !canRead {
|
||||
panic(result.BadRequest("no auth to visit this file."))
|
||||
}
|
||||
|
||||
var expireTime time.Time
|
||||
@@ -338,14 +355,14 @@ func (this *AlienController) FetchDownloadToken(writer http.ResponseWriter, requ
|
||||
|
||||
}
|
||||
|
||||
//preview a file.
|
||||
// preview a file.
|
||||
func (this *AlienController) Preview(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
|
||||
|
||||
this.alienService.PreviewOrDownload(writer, request, uuid, filename, false)
|
||||
matter := this.alienService.ValidMatter(writer, request, uuid, filename)
|
||||
this.alienService.PreviewOrDownload(writer, request, matter, false)
|
||||
}
|
||||
|
||||
//download a file.
|
||||
// download a file.
|
||||
func (this *AlienController) Download(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
|
||||
|
||||
this.alienService.PreviewOrDownload(writer, request, uuid, filename, true)
|
||||
matter := this.alienService.ValidMatter(writer, request, uuid, filename)
|
||||
this.alienService.PreviewOrDownload(writer, request, matter, true)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//@Service
|
||||
// @Service
|
||||
type AlienService struct {
|
||||
BaseBean
|
||||
matterDao *MatterDao
|
||||
@@ -19,6 +19,7 @@ type AlienService struct {
|
||||
shareService *ShareService
|
||||
imageCacheDao *ImageCacheDao
|
||||
imageCacheService *ImageCacheService
|
||||
spaceService *SpaceService
|
||||
}
|
||||
|
||||
func (this *AlienService) Init() {
|
||||
@@ -63,14 +64,18 @@ func (this *AlienService) Init() {
|
||||
if c, ok := b.(*ImageCacheService); ok {
|
||||
this.imageCacheService = c
|
||||
}
|
||||
b = core.CONTEXT.GetBean(this.spaceService)
|
||||
if c, ok := b.(*SpaceService); ok {
|
||||
this.spaceService = c
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AlienService) PreviewOrDownload(
|
||||
// check whether the request params ok.
|
||||
func (this *AlienService) ValidMatter(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
uuid string,
|
||||
filename string,
|
||||
withContentDisposition bool) {
|
||||
filename string) *Matter {
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
|
||||
@@ -94,8 +99,10 @@ func (this *AlienService) PreviewOrDownload(
|
||||
}
|
||||
|
||||
tokenUser := this.userDao.CheckByUuid(downloadToken.UserUuid)
|
||||
if matter.UserUuid != tokenUser.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
|
||||
if matter.SpaceUuid != tokenUser.SpaceUuid {
|
||||
//whether user has the space's read auth.
|
||||
this.spaceService.CheckReadableByUuid(request, tokenUser, matter.SpaceUuid)
|
||||
}
|
||||
|
||||
//TODO: expire the download token. If download by chunk, do this later.
|
||||
@@ -104,18 +111,28 @@ func (this *AlienService) PreviewOrDownload(
|
||||
|
||||
} else {
|
||||
|
||||
//whether this is myself's matter.
|
||||
operator := this.findUser(request)
|
||||
if operator == nil {
|
||||
panic(result.BadRequest("no auth"))
|
||||
}
|
||||
|
||||
//use share code to auth.
|
||||
shareUuid := request.FormValue("shareUuid")
|
||||
shareCode := request.FormValue("shareCode")
|
||||
shareRootUuid := request.FormValue("shareRootUuid")
|
||||
|
||||
this.shareService.ValidateMatter(request, shareUuid, shareCode, operator, shareRootUuid, matter)
|
||||
if matter.SpaceUuid != operator.SpaceUuid {
|
||||
//whether user has the space's read auth.
|
||||
this.spaceService.CheckReadableByUuid(request, operator, matter.SpaceUuid)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return matter
|
||||
}
|
||||
|
||||
func (this *AlienService) PreviewOrDownload(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
matter *Matter,
|
||||
withContentDisposition bool,
|
||||
) {
|
||||
//download directory
|
||||
if matter.Dir {
|
||||
|
||||
@@ -135,7 +152,7 @@ func (this *AlienService) PreviewOrDownload(
|
||||
}
|
||||
|
||||
//download the cache image file.
|
||||
this.matterService.DownloadFile(writer, request, GetUserCacheRootDir(imageCache.Username)+imageCache.Path, imageCache.Name, withContentDisposition)
|
||||
this.matterService.DownloadFile(writer, request, GetSpaceCacheRootDir(imageCache.Username)+imageCache.Path, imageCache.Name, withContentDisposition)
|
||||
|
||||
} else {
|
||||
this.matterService.DownloadFile(writer, request, matter.AbsolutePath(), matter.Name, withContentDisposition)
|
||||
@@ -145,7 +162,6 @@ func (this *AlienService) PreviewOrDownload(
|
||||
|
||||
//async increase the download times.
|
||||
go core.RunWithRecovery(func() {
|
||||
this.matterDao.TimesIncrement(uuid)
|
||||
this.matterDao.TimesIncrement(matter.Uuid)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ import (
|
||||
|
||||
type BaseController struct {
|
||||
BaseBean
|
||||
userDao *UserDao
|
||||
sessionDao *SessionDao
|
||||
userDao *UserDao
|
||||
spaceDao *SpaceDao
|
||||
spaceService *SpaceService
|
||||
sessionDao *SessionDao
|
||||
}
|
||||
|
||||
func (this *BaseController) Init() {
|
||||
@@ -26,6 +28,16 @@ func (this *BaseController) Init() {
|
||||
this.userDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceService)
|
||||
if b, ok := b.(*SpaceService); ok {
|
||||
this.spaceService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.sessionDao)
|
||||
if b, ok := b.(*SessionDao); ok {
|
||||
this.sessionDao = b
|
||||
@@ -38,12 +50,12 @@ func (this *BaseController) RegisterRoutes() map[string]func(writer http.Respons
|
||||
return make(map[string]func(writer http.ResponseWriter, request *http.Request))
|
||||
}
|
||||
|
||||
//handle some special routes, eg. params in the url.
|
||||
// handle some special routes, eg. params in the url.
|
||||
func (this *BaseController) HandleRoutes(writer http.ResponseWriter, request *http.Request) (func(writer http.ResponseWriter, request *http.Request), bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
//wrap the handle method.
|
||||
// wrap the handle method.
|
||||
func (this *BaseController) Wrap(f func(writer http.ResponseWriter, request *http.Request) *result.WebResult, qualifiedRole string) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
@@ -87,7 +99,50 @@ func (this *BaseController) Wrap(f func(writer http.ResponseWriter, request *htt
|
||||
}
|
||||
}
|
||||
|
||||
//response a success result. 1.string 2. WebResult 3.nil pointer 4.any type
|
||||
// wrap the handle method without result.
|
||||
func (this *BaseController) WrapPure(f func(writer http.ResponseWriter, request *http.Request), qualifiedRole string) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
var webResult *result.WebResult = nil
|
||||
|
||||
//if the api not annotated with GUEST. login is required.
|
||||
if qualifiedRole != USER_ROLE_GUEST {
|
||||
user := this.checkUser(request)
|
||||
|
||||
if user.Status == USER_STATUS_DISABLED {
|
||||
//check user's status
|
||||
webResult = result.CustomWebResultI18n(request, result.USER_DISABLED, i18n.UserDisabled)
|
||||
} else {
|
||||
if qualifiedRole == USER_ROLE_ADMINISTRATOR && user.Role != USER_ROLE_ADMINISTRATOR {
|
||||
webResult = result.ConstWebResult(result.UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//if webResult not nil. response a json. if webResult is nil, return empty body or binary content.
|
||||
if webResult != nil {
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
b, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(webResult)
|
||||
|
||||
this.PanicError(err)
|
||||
|
||||
writer.WriteHeader(result.FetchHttpStatus(webResult.Code))
|
||||
|
||||
_, err = fmt.Fprintf(writer, string(b))
|
||||
this.PanicError(err)
|
||||
}
|
||||
|
||||
//no error.
|
||||
f(writer, request)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// response a success result. 1.string 2. WebResult 3.nil pointer 4.any type
|
||||
func (this *BaseController) Success(data interface{}) *result.WebResult {
|
||||
var webResult *result.WebResult = nil
|
||||
if value, ok := data.(string); ok {
|
||||
@@ -106,7 +161,7 @@ func (this *BaseController) Success(data interface{}) *result.WebResult {
|
||||
return webResult
|
||||
}
|
||||
|
||||
//allow cors.
|
||||
// allow cors.
|
||||
func (this *BaseController) allowCORS(writer http.ResponseWriter) {
|
||||
util.AllowCORS(writer)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package rest
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -16,24 +15,6 @@ const (
|
||||
EMPTY_JSON_ARRAY = "[]"
|
||||
)
|
||||
|
||||
type IBase interface {
|
||||
//name of db table
|
||||
TableName() string
|
||||
}
|
||||
|
||||
// Mysql 5.5 only support one CURRENT_TIMESTAMP
|
||||
// so we use 2018-01-01 00:00:00 as default, which is the first release date of EyeblueTank
|
||||
type Base struct {
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
}
|
||||
|
||||
func (this *Base) TableName() string {
|
||||
panic("you should overwrite TableName()")
|
||||
}
|
||||
|
||||
//pager
|
||||
type Pager struct {
|
||||
Page int `json:"page"`
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ func (this *BridgeDao) PlainPage(page int, pageSize int, shareUuid string, sortA
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Bridge{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
@@ -77,7 +77,7 @@ func (this *BridgeDao) PlainPage(page int, pageSize int, shareUuid string, sortA
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&bridges)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return count, bridges
|
||||
return int(count), bridges
|
||||
}
|
||||
|
||||
//get pager
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
)
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* the link table for Share and Matter.
|
||||
*/
|
||||
type Bridge struct {
|
||||
Base
|
||||
ShareUuid string `json:"shareUuid" gorm:"type:char(36)"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36)"`
|
||||
}
|
||||
|
||||
func (this *Bridge) TableName() string {
|
||||
return core.TABLE_PREFIX + "bridge"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
ShareUuid string `json:"shareUuid" gorm:"type:char(36)"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36)"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ChunkController handles chunked upload API endpoints
|
||||
type ChunkController struct {
|
||||
BaseController
|
||||
chunkService *ChunkService
|
||||
matterDao *MatterDao
|
||||
uploadSessionDao *UploadSessionDao
|
||||
uploadChunkDao *UploadChunkDao
|
||||
}
|
||||
|
||||
func (this *ChunkController) Init() {
|
||||
this.BaseController.Init()
|
||||
|
||||
b := core.CONTEXT.GetBean(this.chunkService)
|
||||
if b, ok := b.(*ChunkService); ok {
|
||||
this.chunkService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterDao)
|
||||
if b, ok := b.(*MatterDao); ok {
|
||||
this.matterDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.uploadSessionDao)
|
||||
if b, ok := b.(*UploadSessionDao); ok {
|
||||
this.uploadSessionDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.uploadChunkDao)
|
||||
if b, ok := b.(*UploadChunkDao); ok {
|
||||
this.uploadChunkDao = b
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ChunkController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
|
||||
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
|
||||
|
||||
// Chunked upload endpoints
|
||||
routeMap["/api/chunk/create/session"] = this.Wrap(this.CreateSession, USER_ROLE_USER)
|
||||
routeMap["/api/chunk/upload"] = this.Wrap(this.Upload, USER_ROLE_USER)
|
||||
routeMap["/api/chunk/merge"] = this.Wrap(this.Merge, USER_ROLE_USER)
|
||||
routeMap["/api/chunk/session/info"] = this.Wrap(this.SessionInfo, USER_ROLE_USER)
|
||||
routeMap["/api/chunk/cancel"] = this.Wrap(this.Cancel, USER_ROLE_USER)
|
||||
routeMap["/api/chunk/clean/expired"] = this.Wrap(this.CleanExpired, USER_ROLE_ADMINISTRATOR)
|
||||
|
||||
return routeMap
|
||||
}
|
||||
|
||||
// CreateSession creates a new chunked upload session
|
||||
// Parameters:
|
||||
// - puuid: parent directory uuid
|
||||
// - filename: target filename
|
||||
// - totalSize: total file size in bytes
|
||||
// - chunkSize: size of each chunk (optional, default 5MB)
|
||||
// - privacy: whether the file is private (optional, default true)
|
||||
// - fileMd5: MD5 of the whole file (optional, for verification)
|
||||
// - spaceUuid: space uuid (optional, default user's private space)
|
||||
func (this *ChunkController) CreateSession(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
puuid := util.ExtractRequestString(request, "puuid")
|
||||
filename := util.ExtractRequestString(request, "filename")
|
||||
totalSize := util.ExtractRequestInt64(request, "totalSize")
|
||||
chunkSize := util.ExtractRequestOptionalInt64(request, "chunkSize", DEFAULT_CHUNK_SIZE)
|
||||
privacy := util.ExtractRequestOptionalBool(request, "privacy", true)
|
||||
fileMd5 := util.ExtractRequestOptionalString(request, "fileMd5", "")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
dirMatter := this.matterDao.CheckWithRootByUuid(puuid, space)
|
||||
|
||||
session := this.chunkService.CreateSession(request, user, space, dirMatter, filename, totalSize, chunkSize, privacy, fileMd5)
|
||||
|
||||
return this.Success(session)
|
||||
}
|
||||
|
||||
// Upload uploads a single chunk
|
||||
// Parameters:
|
||||
// - sessionUuid: upload session uuid
|
||||
// - chunkIndex: chunk index (0-based)
|
||||
// - file: chunk file data
|
||||
// - chunkMd5: MD5 of the chunk (optional, for verification)
|
||||
// - spaceUuid: space uuid (optional)
|
||||
func (this *ChunkController) Upload(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
sessionUuid := util.ExtractRequestString(request, "sessionUuid")
|
||||
chunkIndex := util.ExtractRequestInt(request, "chunkIndex")
|
||||
chunkMd5 := util.ExtractRequestOptionalString(request, "chunkMd5", "")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
file, _, err := request.FormFile("file")
|
||||
if err != nil {
|
||||
panic(result.BadRequest("file is required: %s", err.Error()))
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
chunk := this.chunkService.UploadChunk(request, sessionUuid, chunkIndex, file, chunkMd5, user, space)
|
||||
|
||||
return this.Success(chunk)
|
||||
}
|
||||
|
||||
// Merge merges all chunks into the final file
|
||||
// Parameters:
|
||||
// - sessionUuid: upload session uuid
|
||||
// - spaceUuid: space uuid (optional)
|
||||
func (this *ChunkController) Merge(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
sessionUuid := util.ExtractRequestString(request, "sessionUuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
matter := this.chunkService.MergeChunks(request, sessionUuid, user, space)
|
||||
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
// SessionInfo gets the upload session info including uploaded chunks
|
||||
// Parameters:
|
||||
// - sessionUuid: upload session uuid
|
||||
func (this *ChunkController) SessionInfo(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
sessionUuid := util.ExtractRequestString(request, "sessionUuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
|
||||
sessionInfo := this.chunkService.GetSessionInfo(sessionUuid, user)
|
||||
|
||||
return this.Success(sessionInfo)
|
||||
}
|
||||
|
||||
// Cancel cancels an upload session
|
||||
// Parameters:
|
||||
// - sessionUuid: upload session uuid
|
||||
// - spaceUuid: space uuid (optional)
|
||||
func (this *ChunkController) Cancel(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
sessionUuid := util.ExtractRequestString(request, "sessionUuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
this.chunkService.CancelSession(sessionUuid, user, space)
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
// CleanExpired cleans up expired upload sessions (admin only)
|
||||
func (this *ChunkController) CleanExpired(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
this.chunkService.CleanExpiredSessions()
|
||||
return this.Success("OK")
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UploadSessionDao handles database operations for UploadSession
|
||||
type UploadSessionDao struct {
|
||||
BaseDao
|
||||
}
|
||||
|
||||
// FindByUuid finds an upload session by uuid
|
||||
func (this *UploadSessionDao) FindByUuid(sessionUuid string) *UploadSession {
|
||||
var entity = &UploadSession{}
|
||||
db := core.CONTEXT.GetDB().Where("uuid = ?", sessionUuid).First(entity)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// CheckByUuid checks if session exists, panics if not found
|
||||
func (this *UploadSessionDao) CheckByUuid(sessionUuid string) *UploadSession {
|
||||
entity := this.FindByUuid(sessionUuid)
|
||||
if entity == nil {
|
||||
panic(result.NotFound("upload session not found with uuid = %s", sessionUuid))
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// FindByUserUuidAndFileMd5 finds session by user and file MD5 (for quick upload)
|
||||
func (this *UploadSessionDao) FindByUserUuidAndFileMd5(userUuid string, fileMd5 string) *UploadSession {
|
||||
var entity = &UploadSession{}
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ? AND file_md5 = ? AND status = ?",
|
||||
userUuid, fileMd5, UPLOAD_SESSION_STATUS_COMPLETED).First(entity)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// FindBySpaceUuidAndFileMd5 finds completed session by space and file MD5
|
||||
func (this *UploadSessionDao) FindBySpaceUuidAndFileMd5(spaceUuid string, fileMd5 string) *UploadSession {
|
||||
var entity = &UploadSession{}
|
||||
db := core.CONTEXT.GetDB().Where("space_uuid = ? AND file_md5 = ? AND status = ?",
|
||||
spaceUuid, fileMd5, UPLOAD_SESSION_STATUS_COMPLETED).First(entity)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// Create creates a new upload session
|
||||
func (this *UploadSessionDao) Create(session *UploadSession) *UploadSession {
|
||||
timeUUID, _ := uuid.NewV4()
|
||||
session.Uuid = timeUUID.String()
|
||||
session.CreateTime = time.Now()
|
||||
session.UpdateTime = time.Now()
|
||||
session.Sort = time.Now().UnixNano() / 1e6
|
||||
|
||||
db := core.CONTEXT.GetDB().Create(session)
|
||||
this.PanicError(db.Error)
|
||||
return session
|
||||
}
|
||||
|
||||
// Save updates an upload session
|
||||
func (this *UploadSessionDao) Save(session *UploadSession) *UploadSession {
|
||||
session.UpdateTime = time.Now()
|
||||
db := core.CONTEXT.GetDB().Save(session)
|
||||
this.PanicError(db.Error)
|
||||
return session
|
||||
}
|
||||
|
||||
// Delete deletes an upload session
|
||||
func (this *UploadSessionDao) Delete(session *UploadSession) {
|
||||
db := core.CONTEXT.GetDB().Delete(session)
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
// DeleteByUuid deletes session by uuid
|
||||
func (this *UploadSessionDao) DeleteByUuid(sessionUuid string) {
|
||||
db := core.CONTEXT.GetDB().Where("uuid = ?", sessionUuid).Delete(UploadSession{})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
// FindExpiredSessions finds all expired sessions
|
||||
func (this *UploadSessionDao) FindExpiredSessions() []*UploadSession {
|
||||
var sessions []*UploadSession
|
||||
db := core.CONTEXT.GetDB().Where("expire_time < ? AND status = ?",
|
||||
time.Now(), UPLOAD_SESSION_STATUS_UPLOADING).Find(&sessions)
|
||||
this.PanicError(db.Error)
|
||||
return sessions
|
||||
}
|
||||
|
||||
// FindByUserUuid finds all sessions for a user
|
||||
func (this *UploadSessionDao) FindByUserUuid(userUuid string) []*UploadSession {
|
||||
var sessions []*UploadSession
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Find(&sessions)
|
||||
this.PanicError(db.Error)
|
||||
return sessions
|
||||
}
|
||||
|
||||
// DeleteByUserUuid deletes all sessions for a user
|
||||
func (this *UploadSessionDao) DeleteByUserUuid(userUuid string) {
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Delete(UploadSession{})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
// UploadChunkDao handles database operations for UploadChunk
|
||||
type UploadChunkDao struct {
|
||||
BaseDao
|
||||
}
|
||||
|
||||
// FindByUuid finds a chunk by uuid
|
||||
func (this *UploadChunkDao) FindByUuid(chunkUuid string) *UploadChunk {
|
||||
var entity = &UploadChunk{}
|
||||
db := core.CONTEXT.GetDB().Where("uuid = ?", chunkUuid).First(entity)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// FindBySessionUuidAndChunkIndex finds a chunk by session and index
|
||||
func (this *UploadChunkDao) FindBySessionUuidAndChunkIndex(sessionUuid string, chunkIndex int) *UploadChunk {
|
||||
var entity = &UploadChunk{}
|
||||
db := core.CONTEXT.GetDB().Where("session_uuid = ? AND chunk_index = ?", sessionUuid, chunkIndex).First(entity)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// FindBySessionUuid finds all chunks for a session
|
||||
func (this *UploadChunkDao) FindBySessionUuid(sessionUuid string) []*UploadChunk {
|
||||
var chunks []*UploadChunk
|
||||
db := core.CONTEXT.GetDB().Where("session_uuid = ?", sessionUuid).Order("chunk_index asc").Find(&chunks)
|
||||
this.PanicError(db.Error)
|
||||
return chunks
|
||||
}
|
||||
|
||||
// FindUploadedBySessionUuid finds all uploaded chunks for a session
|
||||
func (this *UploadChunkDao) FindUploadedBySessionUuid(sessionUuid string) []*UploadChunk {
|
||||
var chunks []*UploadChunk
|
||||
db := core.CONTEXT.GetDB().Where("session_uuid = ? AND uploaded = ?", sessionUuid, true).Order("chunk_index asc").Find(&chunks)
|
||||
this.PanicError(db.Error)
|
||||
return chunks
|
||||
}
|
||||
|
||||
// CountUploadedBySessionUuid counts uploaded chunks for a session
|
||||
func (this *UploadChunkDao) CountUploadedBySessionUuid(sessionUuid string) int64 {
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().Model(&UploadChunk{}).Where("session_uuid = ? AND uploaded = ?", sessionUuid, true).Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
return count
|
||||
}
|
||||
|
||||
// Create creates a new chunk record
|
||||
func (this *UploadChunkDao) Create(chunk *UploadChunk) *UploadChunk {
|
||||
timeUUID, _ := uuid.NewV4()
|
||||
chunk.Uuid = timeUUID.String()
|
||||
chunk.CreateTime = time.Now()
|
||||
chunk.UpdateTime = time.Now()
|
||||
chunk.Sort = time.Now().UnixNano() / 1e6
|
||||
|
||||
db := core.CONTEXT.GetDB().Create(chunk)
|
||||
this.PanicError(db.Error)
|
||||
return chunk
|
||||
}
|
||||
|
||||
// Save updates a chunk record
|
||||
func (this *UploadChunkDao) Save(chunk *UploadChunk) *UploadChunk {
|
||||
chunk.UpdateTime = time.Now()
|
||||
db := core.CONTEXT.GetDB().Save(chunk)
|
||||
this.PanicError(db.Error)
|
||||
return chunk
|
||||
}
|
||||
|
||||
// Delete deletes a chunk record
|
||||
func (this *UploadChunkDao) Delete(chunk *UploadChunk) {
|
||||
db := core.CONTEXT.GetDB().Delete(chunk)
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
// DeleteBySessionUuid deletes all chunks for a session
|
||||
func (this *UploadChunkDao) DeleteBySessionUuid(sessionUuid string) {
|
||||
db := core.CONTEXT.GetDB().Where("session_uuid = ?", sessionUuid).Delete(UploadChunk{})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Upload session status
|
||||
UPLOAD_SESSION_STATUS_UPLOADING = "UPLOADING"
|
||||
UPLOAD_SESSION_STATUS_MERGING = "MERGING"
|
||||
UPLOAD_SESSION_STATUS_COMPLETED = "COMPLETED"
|
||||
UPLOAD_SESSION_STATUS_ERROR = "ERROR"
|
||||
|
||||
// Chunk directory name
|
||||
CHUNK_DIR = "chunk"
|
||||
|
||||
// Default chunk size: 50MB
|
||||
DEFAULT_CHUNK_SIZE = 50 * 1024 * 1024
|
||||
|
||||
// Max chunk size: 100MB
|
||||
MAX_CHUNK_SIZE = 100 * 1024 * 1024
|
||||
|
||||
// Min chunk size: 5MB (aligned with AWS S3 standard)
|
||||
MIN_CHUNK_SIZE = 5 * 1024 * 1024
|
||||
)
|
||||
|
||||
// UploadSession represents a chunked upload session
|
||||
type UploadSession struct {
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36) not null;index:idx_upload_session_uu"`
|
||||
SpaceUuid string `json:"spaceUuid" gorm:"type:char(36) not null;index:idx_upload_session_su"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36)"` // Final matter uuid after merge
|
||||
Puuid string `json:"puuid" gorm:"type:char(36) not null"` // Parent directory uuid
|
||||
Filename string `json:"filename" gorm:"type:varchar(255) not null"` // Target filename
|
||||
TotalSize int64 `json:"totalSize" gorm:"type:bigint(20) not null;default:0"` // Total file size
|
||||
ChunkSize int64 `json:"chunkSize" gorm:"type:bigint(20) not null;default:52428800"` // Size of each chunk (default 50MB)
|
||||
TotalChunks int `json:"totalChunks" gorm:"type:int not null;default:0"` // Total number of chunks
|
||||
Privacy bool `json:"privacy" gorm:"type:tinyint(1) not null;default:1"` // Privacy flag
|
||||
FileMd5 string `json:"fileMd5" gorm:"type:varchar(45)"` // MD5 of the whole file
|
||||
Status string `json:"status" gorm:"type:varchar(20) not null;default:'UPLOADING'"` // Session status
|
||||
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"` // Expiration time
|
||||
}
|
||||
|
||||
// UploadChunk represents a single chunk in an upload session
|
||||
type UploadChunk struct {
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
SessionUuid string `json:"sessionUuid" gorm:"type:char(36) not null;index:idx_upload_chunk_su"` // Reference to UploadSession
|
||||
ChunkIndex int `json:"chunkIndex" gorm:"type:int not null"` // Chunk sequence number (0-based)
|
||||
ChunkSize int64 `json:"chunkSize" gorm:"type:bigint(20) not null;default:0"` // Actual size of this chunk
|
||||
ChunkMd5 string `json:"chunkMd5" gorm:"type:varchar(45)"` // MD5 of this chunk
|
||||
ChunkFilename string `json:"chunkFilename" gorm:"type:varchar(255) not null"` // Temp filename for this chunk
|
||||
Uploaded bool `json:"uploaded" gorm:"type:tinyint(1) not null;default:0"` // Whether this chunk is uploaded
|
||||
}
|
||||
|
||||
// Get the chunk temp directory for a space
|
||||
func GetSpaceChunkDir(spaceName string) string {
|
||||
return GetUserSpaceRootDir(spaceName) + "/" + CHUNK_DIR
|
||||
}
|
||||
|
||||
// Get the session chunk directory
|
||||
func GetSessionChunkDir(spaceName string, sessionUuid string) string {
|
||||
return GetSpaceChunkDir(spaceName) + "/" + sessionUuid
|
||||
}
|
||||
|
||||
// UploadSessionInfo is the response structure for session queries
|
||||
type UploadSessionInfo struct {
|
||||
Session *UploadSession `json:"session"`
|
||||
UploadedChunks []int `json:"uploadedChunks"` // List of uploaded chunk indices
|
||||
MissingChunks []int `json:"missingChunks"` // List of missing chunk indices
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
)
|
||||
|
||||
// ChunkService handles chunked upload operations
|
||||
//@Service
|
||||
type ChunkService struct {
|
||||
BaseBean
|
||||
uploadSessionDao *UploadSessionDao
|
||||
uploadChunkDao *UploadChunkDao
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
spaceDao *SpaceDao
|
||||
userService *UserService
|
||||
// chunkLocker is used to prevent concurrent uploads of the same chunk
|
||||
chunkLocker sync.Map
|
||||
// mergeLocker is used to prevent concurrent merge of the same session
|
||||
mergeLocker sync.Map
|
||||
}
|
||||
|
||||
func (this *ChunkService) Init() {
|
||||
this.BaseBean.Init()
|
||||
|
||||
b := core.CONTEXT.GetBean(this.uploadSessionDao)
|
||||
if b, ok := b.(*UploadSessionDao); ok {
|
||||
this.uploadSessionDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.uploadChunkDao)
|
||||
if b, ok := b.(*UploadChunkDao); ok {
|
||||
this.uploadChunkDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterDao)
|
||||
if b, ok := b.(*MatterDao); ok {
|
||||
this.matterDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterService)
|
||||
if b, ok := b.(*MatterService); ok {
|
||||
this.matterService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.userService)
|
||||
if b, ok := b.(*UserService); ok {
|
||||
this.userService = b
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSession creates a new upload session
|
||||
func (this *ChunkService) CreateSession(
|
||||
request *http.Request,
|
||||
user *User,
|
||||
space *Space,
|
||||
dirMatter *Matter,
|
||||
filename string,
|
||||
totalSize int64,
|
||||
chunkSize int64,
|
||||
privacy bool,
|
||||
fileMd5 string,
|
||||
) *UploadSession {
|
||||
|
||||
if user == nil {
|
||||
panic(result.BadRequest("user cannot be nil."))
|
||||
}
|
||||
|
||||
if dirMatter == nil {
|
||||
panic(result.BadRequest("dirMatter cannot be nil."))
|
||||
}
|
||||
|
||||
if dirMatter.Deleted {
|
||||
panic(result.BadRequest("Dir has been deleted. Cannot upload under it."))
|
||||
}
|
||||
|
||||
// Validate filename
|
||||
if len(filename) > MATTER_NAME_MAX_LENGTH {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterNameLengthExceedLimit, len(filename), MATTER_NAME_MAX_LENGTH))
|
||||
}
|
||||
CheckMatterName(request, filename)
|
||||
|
||||
// Validate chunk size
|
||||
if chunkSize < MIN_CHUNK_SIZE {
|
||||
chunkSize = MIN_CHUNK_SIZE
|
||||
}
|
||||
if chunkSize > MAX_CHUNK_SIZE {
|
||||
chunkSize = MAX_CHUNK_SIZE
|
||||
}
|
||||
|
||||
// Validate total size
|
||||
if totalSize <= 0 {
|
||||
panic(result.BadRequest("totalSize must be positive"))
|
||||
}
|
||||
|
||||
// Check space size limit
|
||||
if space.SizeLimit >= 0 {
|
||||
if totalSize > space.SizeLimit {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterSizeExceedLimit, util.HumanFileSize(totalSize), util.HumanFileSize(space.SizeLimit)))
|
||||
}
|
||||
}
|
||||
|
||||
// Check total space limit
|
||||
if space.TotalSizeLimit >= 0 {
|
||||
if space.TotalSize+totalSize > space.TotalSizeLimit {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterSizeExceedTotalLimit, util.HumanFileSize(space.TotalSize), util.HumanFileSize(space.TotalSizeLimit)))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if file already exists
|
||||
dbMatter := this.matterDao.FindBySpaceUuidAndPuuidAndDirAndName(space.Uuid, dirMatter.Uuid, false, filename)
|
||||
if dbMatter != nil {
|
||||
if dbMatter.Deleted {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterRecycleBinExist, filename))
|
||||
} else {
|
||||
panic(result.BadRequestI18n(request, i18n.MatterExist, filename))
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total chunks
|
||||
totalChunks := int((totalSize + chunkSize - 1) / chunkSize)
|
||||
|
||||
// Create session
|
||||
session := &UploadSession{
|
||||
UserUuid: user.Uuid,
|
||||
SpaceUuid: space.Uuid,
|
||||
Puuid: dirMatter.Uuid,
|
||||
Filename: filename,
|
||||
TotalSize: totalSize,
|
||||
ChunkSize: chunkSize,
|
||||
TotalChunks: totalChunks,
|
||||
Privacy: privacy,
|
||||
FileMd5: fileMd5,
|
||||
Status: UPLOAD_SESSION_STATUS_UPLOADING,
|
||||
ExpireTime: time.Now().Add(24 * time.Hour), // 24 hours expiration
|
||||
}
|
||||
|
||||
session = this.uploadSessionDao.Create(session)
|
||||
|
||||
// Create chunk directory
|
||||
chunkDir := GetSessionChunkDir(space.Name, session.Uuid)
|
||||
util.MakeDirAll(chunkDir)
|
||||
|
||||
this.logger.Info("Created upload session %s for file %s, total chunks: %d", session.Uuid, filename, totalChunks)
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
// UploadChunk uploads a single chunk with optional MD5 verification
|
||||
func (this *ChunkService) UploadChunk(
|
||||
request *http.Request,
|
||||
sessionUuid string,
|
||||
chunkIndex int,
|
||||
file io.Reader,
|
||||
clientChunkMd5 string,
|
||||
user *User,
|
||||
space *Space,
|
||||
) *UploadChunk {
|
||||
|
||||
// Check session
|
||||
session := this.uploadSessionDao.CheckByUuid(sessionUuid)
|
||||
|
||||
// Validate session ownership
|
||||
if session.UserUuid != user.Uuid {
|
||||
panic(result.BadRequest("session does not belong to you"))
|
||||
}
|
||||
|
||||
if session.Status != UPLOAD_SESSION_STATUS_UPLOADING {
|
||||
panic(result.BadRequest("session status is not uploading"))
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if time.Now().After(session.ExpireTime) {
|
||||
panic(result.BadRequest("upload session has expired"))
|
||||
}
|
||||
|
||||
// Validate chunk index
|
||||
if chunkIndex < 0 || chunkIndex >= session.TotalChunks {
|
||||
panic(result.BadRequest("invalid chunk index: %d, total chunks: %d", chunkIndex, session.TotalChunks))
|
||||
}
|
||||
|
||||
// Acquire chunk-level lock to prevent concurrent upload of the same chunk
|
||||
chunkLockKey := fmt.Sprintf("%s_%d", sessionUuid, chunkIndex)
|
||||
if _, loaded := this.chunkLocker.LoadOrStore(chunkLockKey, true); loaded {
|
||||
panic(result.BadRequest("chunk %d is being uploaded by another request, please retry later", chunkIndex))
|
||||
}
|
||||
defer this.chunkLocker.Delete(chunkLockKey)
|
||||
|
||||
// Check if chunk already exists
|
||||
existingChunk := this.uploadChunkDao.FindBySessionUuidAndChunkIndex(sessionUuid, chunkIndex)
|
||||
if existingChunk != nil && existingChunk.Uploaded {
|
||||
this.logger.Info("Chunk %d already uploaded for session %s", chunkIndex, sessionUuid)
|
||||
return existingChunk
|
||||
}
|
||||
|
||||
// Prepare chunk file path
|
||||
chunkFilename := fmt.Sprintf("chunk_%d", chunkIndex)
|
||||
chunkDir := GetSessionChunkDir(space.Name, sessionUuid)
|
||||
chunkFilePath := chunkDir + "/" + chunkFilename
|
||||
|
||||
// Write chunk to disk (use 0600 for security - only owner can read/write)
|
||||
destFile, err := os.OpenFile(chunkFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
panic(result.BadRequest("failed to create chunk file: %s", err.Error()))
|
||||
}
|
||||
|
||||
// Calculate MD5 while writing
|
||||
hash := md5.New()
|
||||
multiWriter := io.MultiWriter(destFile, hash)
|
||||
|
||||
chunkSize, err := io.Copy(multiWriter, file)
|
||||
destFile.Close()
|
||||
|
||||
if err != nil {
|
||||
os.Remove(chunkFilePath)
|
||||
panic(result.BadRequest("failed to write chunk: %s", err.Error()))
|
||||
}
|
||||
|
||||
// Validate chunk size to prevent malicious oversized uploads
|
||||
isLastChunk := chunkIndex == session.TotalChunks-1
|
||||
if isLastChunk {
|
||||
// Last chunk: should be <= session.ChunkSize and match expected remainder
|
||||
expectedLastChunkSize := session.TotalSize - int64(session.TotalChunks-1)*session.ChunkSize
|
||||
if chunkSize != expectedLastChunkSize {
|
||||
os.Remove(chunkFilePath)
|
||||
panic(result.BadRequest("last chunk size mismatch: expected %d, got %d", expectedLastChunkSize, chunkSize))
|
||||
}
|
||||
} else {
|
||||
// Non-last chunks: should be exactly session.ChunkSize
|
||||
if chunkSize != session.ChunkSize {
|
||||
os.Remove(chunkFilePath)
|
||||
panic(result.BadRequest("chunk size mismatch: expected %d, got %d", session.ChunkSize, chunkSize))
|
||||
}
|
||||
}
|
||||
|
||||
chunkMd5 := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
// Verify chunk MD5 if client provided one
|
||||
if clientChunkMd5 != "" && clientChunkMd5 != chunkMd5 {
|
||||
os.Remove(chunkFilePath)
|
||||
this.logger.Error("Chunk MD5 verification failed for chunk %d: client=%s, server=%s", chunkIndex, clientChunkMd5, chunkMd5)
|
||||
panic(result.BadRequest("chunk MD5 verification failed: data may be corrupted during upload"))
|
||||
}
|
||||
|
||||
this.logger.Info("Uploaded chunk %d for session %s, size: %d, md5: %s", chunkIndex, sessionUuid, chunkSize, chunkMd5)
|
||||
|
||||
// Create or update chunk record
|
||||
var chunk *UploadChunk
|
||||
if existingChunk != nil {
|
||||
existingChunk.ChunkSize = chunkSize
|
||||
existingChunk.ChunkMd5 = chunkMd5
|
||||
existingChunk.ChunkFilename = chunkFilename
|
||||
existingChunk.Uploaded = true
|
||||
chunk = this.uploadChunkDao.Save(existingChunk)
|
||||
} else {
|
||||
chunk = &UploadChunk{
|
||||
SessionUuid: sessionUuid,
|
||||
ChunkIndex: chunkIndex,
|
||||
ChunkSize: chunkSize,
|
||||
ChunkMd5: chunkMd5,
|
||||
ChunkFilename: chunkFilename,
|
||||
Uploaded: true,
|
||||
}
|
||||
chunk = this.uploadChunkDao.Create(chunk)
|
||||
}
|
||||
|
||||
return chunk
|
||||
}
|
||||
|
||||
// GetSessionInfo gets the upload session info including uploaded chunks
|
||||
func (this *ChunkService) GetSessionInfo(sessionUuid string, user *User) *UploadSessionInfo {
|
||||
session := this.uploadSessionDao.CheckByUuid(sessionUuid)
|
||||
|
||||
// Validate ownership
|
||||
if session.UserUuid != user.Uuid {
|
||||
panic(result.BadRequest("session does not belong to you"))
|
||||
}
|
||||
|
||||
// Get uploaded chunks
|
||||
uploadedChunks := this.uploadChunkDao.FindUploadedBySessionUuid(sessionUuid)
|
||||
|
||||
uploadedIndices := make([]int, 0)
|
||||
uploadedMap := make(map[int]bool)
|
||||
for _, chunk := range uploadedChunks {
|
||||
uploadedIndices = append(uploadedIndices, chunk.ChunkIndex)
|
||||
uploadedMap[chunk.ChunkIndex] = true
|
||||
}
|
||||
|
||||
// Calculate missing chunks
|
||||
missingIndices := make([]int, 0)
|
||||
for i := 0; i < session.TotalChunks; i++ {
|
||||
if !uploadedMap[i] {
|
||||
missingIndices = append(missingIndices, i)
|
||||
}
|
||||
}
|
||||
|
||||
return &UploadSessionInfo{
|
||||
Session: session,
|
||||
UploadedChunks: uploadedIndices,
|
||||
MissingChunks: missingIndices,
|
||||
}
|
||||
}
|
||||
|
||||
// MergeChunks merges all chunks into final file
|
||||
func (this *ChunkService) MergeChunks(
|
||||
request *http.Request,
|
||||
sessionUuid string,
|
||||
user *User,
|
||||
space *Space,
|
||||
) *Matter {
|
||||
|
||||
// Use session-level lock instead of user-level lock to allow concurrent uploads of different files
|
||||
if _, loaded := this.mergeLocker.LoadOrStore(sessionUuid, true); loaded {
|
||||
panic(result.BadRequest("merge operation is already in progress for this session"))
|
||||
}
|
||||
defer this.mergeLocker.Delete(sessionUuid)
|
||||
|
||||
session := this.uploadSessionDao.CheckByUuid(sessionUuid)
|
||||
|
||||
// Validate ownership
|
||||
if session.UserUuid != user.Uuid {
|
||||
panic(result.BadRequest("session does not belong to you"))
|
||||
}
|
||||
|
||||
// Check session expiration
|
||||
if time.Now().After(session.ExpireTime) {
|
||||
panic(result.BadRequest("upload session has expired, please start a new upload"))
|
||||
}
|
||||
|
||||
if session.Status == UPLOAD_SESSION_STATUS_COMPLETED {
|
||||
// Already completed, return the matter
|
||||
if session.MatterUuid != "" {
|
||||
return this.matterDao.CheckByUuid(session.MatterUuid)
|
||||
}
|
||||
panic(result.BadRequest("session completed but matter not found"))
|
||||
}
|
||||
|
||||
// Allow retry from ERROR status (e.g., previous merge failed)
|
||||
if session.Status != UPLOAD_SESSION_STATUS_UPLOADING && session.Status != UPLOAD_SESSION_STATUS_ERROR {
|
||||
panic(result.BadRequest("session status is not uploading or error, current: %s", session.Status))
|
||||
}
|
||||
|
||||
// If retrying from ERROR status, reset to UPLOADING first
|
||||
if session.Status == UPLOAD_SESSION_STATUS_ERROR {
|
||||
this.logger.Info("Retrying merge for session %s from ERROR status", sessionUuid)
|
||||
session.Status = UPLOAD_SESSION_STATUS_UPLOADING
|
||||
this.uploadSessionDao.Save(session)
|
||||
}
|
||||
|
||||
// Check if all chunks are uploaded
|
||||
uploadedCount := this.uploadChunkDao.CountUploadedBySessionUuid(sessionUuid)
|
||||
if int(uploadedCount) < session.TotalChunks {
|
||||
panic(result.BadRequest("not all chunks uploaded: %d/%d", uploadedCount, session.TotalChunks))
|
||||
}
|
||||
|
||||
// Update status to merging
|
||||
session.Status = UPLOAD_SESSION_STATUS_MERGING
|
||||
this.uploadSessionDao.Save(session)
|
||||
|
||||
// Get parent directory
|
||||
var dirMatter *Matter
|
||||
if session.Puuid == MATTER_ROOT {
|
||||
dirMatter = NewRootMatter(space)
|
||||
} else {
|
||||
dirMatter = this.matterDao.CheckByUuid(session.Puuid)
|
||||
}
|
||||
|
||||
// Prepare final file path
|
||||
dirAbsolutePath := dirMatter.AbsolutePath()
|
||||
util.MakeDirAll(dirAbsolutePath)
|
||||
fileAbsolutePath := dirAbsolutePath + "/" + session.Filename
|
||||
|
||||
// Check if file exists
|
||||
if util.PathExists(fileAbsolutePath) {
|
||||
this.logger.Error("%s exists, removing it.", fileAbsolutePath)
|
||||
os.Remove(fileAbsolutePath)
|
||||
}
|
||||
|
||||
// Create final file (use 0644 for normal file permissions)
|
||||
destFile, err := os.OpenFile(fileAbsolutePath, os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
session.Status = UPLOAD_SESSION_STATUS_ERROR
|
||||
this.uploadSessionDao.Save(session)
|
||||
panic(result.BadRequest("failed to create destination file: %s", err.Error()))
|
||||
}
|
||||
|
||||
// Get all chunks sorted by index
|
||||
chunks := this.uploadChunkDao.FindBySessionUuid(sessionUuid)
|
||||
sort.Slice(chunks, func(i, j int) bool {
|
||||
return chunks[i].ChunkIndex < chunks[j].ChunkIndex
|
||||
})
|
||||
|
||||
chunkDir := GetSessionChunkDir(space.Name, sessionUuid)
|
||||
|
||||
// Merge chunks
|
||||
var totalWritten int64 = 0
|
||||
for _, chunk := range chunks {
|
||||
chunkPath := chunkDir + "/" + chunk.ChunkFilename
|
||||
chunkFile, err := os.Open(chunkPath)
|
||||
if err != nil {
|
||||
destFile.Close()
|
||||
os.Remove(fileAbsolutePath)
|
||||
session.Status = UPLOAD_SESSION_STATUS_ERROR
|
||||
this.uploadSessionDao.Save(session)
|
||||
panic(result.BadRequest("failed to open chunk %d: %s", chunk.ChunkIndex, err.Error()))
|
||||
}
|
||||
|
||||
written, err := io.Copy(destFile, chunkFile)
|
||||
chunkFile.Close()
|
||||
|
||||
if err != nil {
|
||||
destFile.Close()
|
||||
os.Remove(fileAbsolutePath)
|
||||
session.Status = UPLOAD_SESSION_STATUS_ERROR
|
||||
this.uploadSessionDao.Save(session)
|
||||
panic(result.BadRequest("failed to write chunk %d: %s", chunk.ChunkIndex, err.Error()))
|
||||
}
|
||||
|
||||
totalWritten += written
|
||||
}
|
||||
|
||||
destFile.Close()
|
||||
|
||||
this.logger.Info("Merged %d chunks into file %s, total size: %d", len(chunks), session.Filename, totalWritten)
|
||||
|
||||
// Verify file size
|
||||
if totalWritten != session.TotalSize {
|
||||
os.Remove(fileAbsolutePath)
|
||||
session.Status = UPLOAD_SESSION_STATUS_ERROR
|
||||
this.uploadSessionDao.Save(session)
|
||||
panic(result.BadRequest("merged file size mismatch: expected %d, got %d", session.TotalSize, totalWritten))
|
||||
}
|
||||
|
||||
// Verify file MD5 if provided
|
||||
if session.FileMd5 != "" {
|
||||
mergedFile, err := os.Open(fileAbsolutePath)
|
||||
if err != nil {
|
||||
os.Remove(fileAbsolutePath)
|
||||
session.Status = UPLOAD_SESSION_STATUS_ERROR
|
||||
this.uploadSessionDao.Save(session)
|
||||
panic(result.BadRequest("failed to open merged file for MD5 verification: %s", err.Error()))
|
||||
}
|
||||
|
||||
hash := md5.New()
|
||||
_, err = io.Copy(hash, mergedFile)
|
||||
mergedFile.Close()
|
||||
|
||||
if err != nil {
|
||||
os.Remove(fileAbsolutePath)
|
||||
session.Status = UPLOAD_SESSION_STATUS_ERROR
|
||||
this.uploadSessionDao.Save(session)
|
||||
panic(result.BadRequest("failed to calculate MD5 of merged file: %s", err.Error()))
|
||||
}
|
||||
|
||||
calculatedMd5 := hex.EncodeToString(hash.Sum(nil))
|
||||
if calculatedMd5 != session.FileMd5 {
|
||||
os.Remove(fileAbsolutePath)
|
||||
session.Status = UPLOAD_SESSION_STATUS_ERROR
|
||||
this.uploadSessionDao.Save(session)
|
||||
this.logger.Error("MD5 verification failed for session %s: expected %s, got %s", sessionUuid, session.FileMd5, calculatedMd5)
|
||||
panic(result.BadRequest("MD5 verification failed: file may be corrupted during upload"))
|
||||
}
|
||||
|
||||
this.logger.Info("MD5 verification passed for session %s", sessionUuid)
|
||||
}
|
||||
|
||||
// Create matter record
|
||||
dirRelativePath := dirMatter.Path
|
||||
fileRelativePath := dirRelativePath + "/" + session.Filename
|
||||
|
||||
matter := &Matter{
|
||||
Puuid: dirMatter.Uuid,
|
||||
UserUuid: user.Uuid,
|
||||
SpaceName: space.Name,
|
||||
SpaceUuid: space.Uuid,
|
||||
Dir: false,
|
||||
Name: session.Filename,
|
||||
Md5: session.FileMd5,
|
||||
Size: totalWritten,
|
||||
Privacy: session.Privacy,
|
||||
Path: fileRelativePath,
|
||||
Prop: EMPTY_JSON_MAP,
|
||||
VisitTime: time.Now(),
|
||||
}
|
||||
matter = this.matterDao.Create(matter)
|
||||
|
||||
// Update session status
|
||||
session.Status = UPLOAD_SESSION_STATUS_COMPLETED
|
||||
session.MatterUuid = matter.Uuid
|
||||
this.uploadSessionDao.Save(session)
|
||||
|
||||
// Clean up chunk files
|
||||
go core.RunWithRecovery(func() {
|
||||
this.cleanupChunks(space.Name, sessionUuid)
|
||||
this.matterService.ComputeRouteSize(dirMatter.Uuid, user, space)
|
||||
})
|
||||
|
||||
this.logger.Info("Upload session %s completed, matter uuid: %s", sessionUuid, matter.Uuid)
|
||||
|
||||
return matter
|
||||
}
|
||||
|
||||
// cleanupChunks removes chunk files and records with improved atomicity
|
||||
// First delete database records, then delete files (safer approach)
|
||||
func (this *ChunkService) cleanupChunks(spaceName string, sessionUuid string) {
|
||||
chunkDir := GetSessionChunkDir(spaceName, sessionUuid)
|
||||
|
||||
// First, remove chunk records from database
|
||||
// This ensures that even if file deletion fails, we don't have orphaned DB records
|
||||
this.uploadChunkDao.DeleteBySessionUuid(sessionUuid)
|
||||
|
||||
// Then remove chunk directory with retry mechanism
|
||||
maxRetries := 3
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
err := os.RemoveAll(chunkDir)
|
||||
if err == nil {
|
||||
this.logger.Info("Cleaned up chunks for session %s", sessionUuid)
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.Error("failed to remove chunk dir %s (attempt %d/%d): %s", chunkDir, i+1, maxRetries, err.Error())
|
||||
|
||||
// Wait before retry (exponential backoff)
|
||||
if i < maxRetries-1 {
|
||||
time.Sleep(time.Duration(100*(i+1)) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// If all retries failed, log a critical warning
|
||||
this.logger.Error("CRITICAL: Failed to remove chunk dir %s after %d attempts. Manual cleanup may be required.", chunkDir, maxRetries)
|
||||
}
|
||||
|
||||
// CancelSession cancels an upload session
|
||||
func (this *ChunkService) CancelSession(sessionUuid string, user *User, space *Space) {
|
||||
session := this.uploadSessionDao.CheckByUuid(sessionUuid)
|
||||
|
||||
// Validate ownership
|
||||
if session.UserUuid != user.Uuid {
|
||||
panic(result.BadRequest("session does not belong to you"))
|
||||
}
|
||||
|
||||
// Clean up
|
||||
this.cleanupChunks(space.Name, sessionUuid)
|
||||
|
||||
// Delete session
|
||||
this.uploadSessionDao.Delete(session)
|
||||
|
||||
this.logger.Info("Cancelled upload session %s", sessionUuid)
|
||||
}
|
||||
|
||||
// CleanExpiredSessions cleans up expired sessions
|
||||
func (this *ChunkService) CleanExpiredSessions() {
|
||||
sessions := this.uploadSessionDao.FindExpiredSessions()
|
||||
|
||||
for _, session := range sessions {
|
||||
space := this.spaceDao.FindByUuid(session.SpaceUuid)
|
||||
if space != nil {
|
||||
this.cleanupChunks(space.Name, session.Uuid)
|
||||
}
|
||||
this.uploadSessionDao.Delete(session)
|
||||
this.logger.Info("Cleaned expired session %s", session.Uuid)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -61,14 +61,14 @@ func (this *DashboardDao) Page(page int, pageSize int, dt string, sortArray []bu
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Dashboard{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var dashboards []*Dashboard
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&dashboards)
|
||||
this.PanicError(db.Error)
|
||||
pager := NewPager(page, pageSize, count, dashboards)
|
||||
pager := NewPager(page, pageSize, int(count), dashboards)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
package rest
|
||||
|
||||
import "github.com/eyebluecn/tank/code/core"
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* application's dashboard.
|
||||
*/
|
||||
type Dashboard struct {
|
||||
Base
|
||||
InvokeNum int64 `json:"invokeNum" gorm:"type:bigint(20) not null"` //api invoke num.
|
||||
TotalInvokeNum int64 `json:"totalInvokeNum" gorm:"type:bigint(20) not null;default:0"` //total invoke num up to now.
|
||||
Uv int64 `json:"uv" gorm:"type:bigint(20) not null;default:0"` //today's uv
|
||||
TotalUv int64 `json:"totalUv" gorm:"type:bigint(20) not null;default:0"` //total uv
|
||||
MatterNum int64 `json:"matterNum" gorm:"type:bigint(20) not null;default:0"` //file's num
|
||||
TotalMatterNum int64 `json:"totalMatterNum" gorm:"type:bigint(20) not null;default:0"` //file's total number
|
||||
FileSize int64 `json:"fileSize" gorm:"type:bigint(20) not null;default:0"` //today's file size
|
||||
TotalFileSize int64 `json:"totalFileSize" gorm:"type:bigint(20) not null;default:0"` //total file's size
|
||||
AvgCost int64 `json:"avgCost" gorm:"type:bigint(20) not null;default:0"` //api time cost in ms
|
||||
Dt string `json:"dt" gorm:"type:varchar(45) not null;index:idx_dt"` //date
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *Dashboard) TableName() string {
|
||||
return core.TABLE_PREFIX + "dashboard"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
InvokeNum int64 `json:"invokeNum" gorm:"type:bigint(20) not null"` //api invoke num.
|
||||
TotalInvokeNum int64 `json:"totalInvokeNum" gorm:"type:bigint(20) not null;default:0"` //total invoke num up to now.
|
||||
Uv int64 `json:"uv" gorm:"type:bigint(20) not null;default:0"` //today's uv
|
||||
TotalUv int64 `json:"totalUv" gorm:"type:bigint(20) not null;default:0"` //total uv
|
||||
MatterNum int64 `json:"matterNum" gorm:"type:bigint(20) not null;default:0"` //file's num
|
||||
TotalMatterNum int64 `json:"totalMatterNum" gorm:"type:bigint(20) not null;default:0"` //file's total number
|
||||
FileSize int64 `json:"fileSize" gorm:"type:bigint(20) not null;default:0"` //today's file size
|
||||
TotalFileSize int64 `json:"totalFileSize" gorm:"type:bigint(20) not null;default:0"` //total file's size
|
||||
AvgCost int64 `json:"avgCost" gorm:"type:bigint(20) not null;default:0"` //api time cost in ms
|
||||
Dt string `json:"dt" gorm:"type:varchar(45) not null;index:idx_dashboard_dt"` //date. index should unique globally for sqlite.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@ type DavController struct {
|
||||
BaseController
|
||||
uploadTokenDao *UploadTokenDao
|
||||
downloadTokenDao *DownloadTokenDao
|
||||
spaceDao *SpaceDao
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
imageCacheDao *ImageCacheDao
|
||||
@@ -50,6 +51,11 @@ func (this *DavController) Init() {
|
||||
this.matterDao = c
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceDao)
|
||||
if c, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = c
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterService)
|
||||
if c, ok := b.(*MatterService); ok {
|
||||
this.matterService = c
|
||||
@@ -71,7 +77,7 @@ func (this *DavController) Init() {
|
||||
}
|
||||
}
|
||||
|
||||
//Auth user by BasicAuth
|
||||
// Auth user by BasicAuth
|
||||
func (this *DavController) CheckCurrentUser(writer http.ResponseWriter, request *http.Request) *User {
|
||||
|
||||
username, password, ok := request.BasicAuth()
|
||||
@@ -100,7 +106,7 @@ func (this *DavController) RegisterRoutes() map[string]func(writer http.Response
|
||||
return routeMap
|
||||
}
|
||||
|
||||
//handle some special routes, eg. params in the url.
|
||||
// handle some special routes, eg. params in the url.
|
||||
func (this *DavController) HandleRoutes(writer http.ResponseWriter, request *http.Request) (func(writer http.ResponseWriter, request *http.Request), bool) {
|
||||
|
||||
path := request.URL.Path
|
||||
@@ -111,10 +117,16 @@ func (this *DavController) HandleRoutes(writer http.ResponseWriter, request *htt
|
||||
strs := reg.FindStringSubmatch(path)
|
||||
if len(strs) == 2 {
|
||||
var f = func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
if err := recover(); err != nil {
|
||||
this.logger.Error("occur error in webdav: %v", err)
|
||||
}
|
||||
|
||||
subPath := strs[1]
|
||||
//guarantee subPath not end with /
|
||||
subPath = strings.TrimSuffix(subPath, "/")
|
||||
this.Index(writer, request, subPath)
|
||||
|
||||
}
|
||||
return f, true
|
||||
}
|
||||
@@ -166,7 +178,8 @@ func (this *DavController) Index(writer http.ResponseWriter, request *http.Reque
|
||||
//this.debug(writer, request, subPath)
|
||||
|
||||
user := this.CheckCurrentUser(writer, request)
|
||||
space := this.spaceDao.CheckByUuid(user.SpaceUuid)
|
||||
|
||||
this.davService.HandleDav(writer, request, user, subPath)
|
||||
this.davService.HandleDav(writer, request, user, space, subPath)
|
||||
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//webdav url prefix.
|
||||
// webdav url prefix.
|
||||
var WEBDAV_PREFIX = "/api/dav"
|
||||
|
||||
//live prop.
|
||||
// live prop.
|
||||
type LiveProp struct {
|
||||
findFn func(user *User, matter *Matter) string
|
||||
findFn func(space *Space, matter *Matter) string
|
||||
dir bool
|
||||
}
|
||||
|
||||
//all live prop map.
|
||||
// all live prop map.
|
||||
var LivePropMap = map[xml.Name]LiveProp{
|
||||
{Space: "DAV:", Local: "resourcetype"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
if matter.Dir {
|
||||
return `<D:collection xmlns:D="DAV:"/>`
|
||||
} else {
|
||||
@@ -31,7 +31,7 @@ var LivePropMap = map[xml.Name]LiveProp{
|
||||
dir: true,
|
||||
},
|
||||
{Space: "DAV:", Local: "displayname"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
if path.Clean("/"+matter.Name) == "/" {
|
||||
return ""
|
||||
} else {
|
||||
@@ -41,13 +41,13 @@ var LivePropMap = map[xml.Name]LiveProp{
|
||||
dir: true,
|
||||
},
|
||||
{Space: "DAV:", Local: "getcontentlength"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
return strconv.FormatInt(matter.Size, 10)
|
||||
},
|
||||
dir: false,
|
||||
},
|
||||
{Space: "DAV:", Local: "getlastmodified"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
return matter.UpdateTime.UTC().Format(http.TimeFormat)
|
||||
},
|
||||
// http://webdav.org/specs/rfc4918.html#PROPERTY_getlastmodified
|
||||
@@ -68,7 +68,7 @@ var LivePropMap = map[xml.Name]LiveProp{
|
||||
dir: false,
|
||||
},
|
||||
{Space: "DAV:", Local: "getcontenttype"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
if matter.Dir {
|
||||
return ""
|
||||
} else {
|
||||
@@ -78,7 +78,7 @@ var LivePropMap = map[xml.Name]LiveProp{
|
||||
dir: false,
|
||||
},
|
||||
{Space: "DAV:", Local: "getetag"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
return fmt.Sprintf(`"%x%x"`, matter.UpdateTime.UnixNano(), matter.Size)
|
||||
},
|
||||
// findETag implements ETag as the concatenated hex values of a file's
|
||||
@@ -91,7 +91,7 @@ var LivePropMap = map[xml.Name]LiveProp{
|
||||
// active locks on a resource.
|
||||
{Space: "DAV:", Local: "lockdiscovery"}: {},
|
||||
{Space: "DAV:", Local: "supportedlock"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
return `` +
|
||||
`<D:lockentry xmlns:D="DAV:">` +
|
||||
`<D:lockscope><D:exclusive/></D:lockscope>` +
|
||||
@@ -101,11 +101,11 @@ var LivePropMap = map[xml.Name]LiveProp{
|
||||
dir: true,
|
||||
},
|
||||
{Space: "DAV:", Local: "quota-available-bytes"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
var size int64 = 0
|
||||
if user.TotalSizeLimit >= 0 {
|
||||
if user.TotalSizeLimit-user.TotalSize > 0 {
|
||||
size = user.TotalSizeLimit - user.TotalSize
|
||||
if space.TotalSizeLimit >= 0 {
|
||||
if space.TotalSizeLimit-space.TotalSize > 0 {
|
||||
size = space.TotalSizeLimit - space.TotalSize
|
||||
} else {
|
||||
size = 0
|
||||
}
|
||||
@@ -118,8 +118,8 @@ var LivePropMap = map[xml.Name]LiveProp{
|
||||
dir: true,
|
||||
},
|
||||
{Space: "DAV:", Local: "quota-used-bytes"}: {
|
||||
findFn: func(user *User, matter *Matter) string {
|
||||
return fmt.Sprintf(`%d`, user.TotalSize)
|
||||
findFn: func(space *Space, matter *Matter) string {
|
||||
return fmt.Sprintf(`%d`, space.TotalSize)
|
||||
},
|
||||
dir: true,
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@ func (this *DavService) Init() {
|
||||
this.lockSystem = webdav.NewMemLS()
|
||||
}
|
||||
|
||||
//get the depth in header. Not support infinity yet.
|
||||
// get the depth in header. Not support infinity yet.
|
||||
func (this *DavService) ParseDepth(request *http.Request) int {
|
||||
|
||||
depth := 1
|
||||
@@ -88,8 +88,8 @@ func (this *DavService) makePropstatResponse(href string, pstats []dav.Propstat)
|
||||
return &resp
|
||||
}
|
||||
|
||||
//fetch a matter's []dav.Propstat
|
||||
func (this *DavService) PropstatsFromXmlNames(user *User, matter *Matter, xmlNames []xml.Name) []dav.Propstat {
|
||||
// fetch a matter's []dav.Propstat
|
||||
func (this *DavService) PropstatsFromXmlNames(user *User, space *Space, matter *Matter, xmlNames []xml.Name) []dav.Propstat {
|
||||
|
||||
propstats := make([]dav.Propstat, 0)
|
||||
|
||||
@@ -101,7 +101,7 @@ func (this *DavService) PropstatsFromXmlNames(user *User, matter *Matter, xmlNam
|
||||
|
||||
// Otherwise, it must either be a live property or we don't know it.
|
||||
if liveProp := LivePropMap[xmlName]; liveProp.findFn != nil && (liveProp.dir || !matter.Dir) {
|
||||
innerXML := liveProp.findFn(user, matter)
|
||||
innerXML := liveProp.findFn(space, matter)
|
||||
|
||||
okProperties = append(okProperties, dav.Property{
|
||||
XMLName: xmlName,
|
||||
@@ -162,7 +162,7 @@ func (this *DavService) AllPropXmlNames(matter *Matter) []xml.Name {
|
||||
return pnames
|
||||
}
|
||||
|
||||
func (this *DavService) Propstats(user *User, matter *Matter, propfind *dav.Propfind) []dav.Propstat {
|
||||
func (this *DavService) Propstats(user *User, space *Space, matter *Matter, propfind *dav.Propfind) []dav.Propstat {
|
||||
|
||||
propstats := make([]dav.Propstat, 0)
|
||||
if propfind.Propname != nil {
|
||||
@@ -172,18 +172,18 @@ func (this *DavService) Propstats(user *User, matter *Matter, propfind *dav.Prop
|
||||
//TODO: if include other things. add to it.
|
||||
xmlNames := this.AllPropXmlNames(matter)
|
||||
|
||||
propstats = this.PropstatsFromXmlNames(user, matter, xmlNames)
|
||||
propstats = this.PropstatsFromXmlNames(user, space, matter, xmlNames)
|
||||
|
||||
} else {
|
||||
propstats = this.PropstatsFromXmlNames(user, matter, propfind.Prop)
|
||||
propstats = this.PropstatsFromXmlNames(user, space, matter, propfind.Prop)
|
||||
}
|
||||
|
||||
return propstats
|
||||
|
||||
}
|
||||
|
||||
//list the directory.
|
||||
func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
// list the directory.
|
||||
func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("PROPFIND %s\n", subPath)
|
||||
|
||||
@@ -193,7 +193,7 @@ func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http
|
||||
propfind := dav.ReadPropfind(request.Body)
|
||||
|
||||
//find the matter, if subPath is null, means the root directory.
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user, space)
|
||||
|
||||
var matters []*Matter
|
||||
if depth == 0 {
|
||||
@@ -213,7 +213,7 @@ func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http
|
||||
|
||||
fmt.Printf("handle Matter %s\n", matter.Path)
|
||||
|
||||
propstats := this.Propstats(user, matter, propfind)
|
||||
propstats := this.Propstats(user, space, matter, propfind)
|
||||
visitPath := fmt.Sprintf("%s%s", WEBDAV_PREFIX, matter.Path)
|
||||
response := this.makePropstatResponse(visitPath, propstats)
|
||||
|
||||
@@ -228,7 +228,7 @@ func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http
|
||||
|
||||
}
|
||||
|
||||
//change the file's property
|
||||
// change the file's property
|
||||
func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
|
||||
fmt.Printf("PROPPATCH %s\n", subPath)
|
||||
@@ -251,7 +251,7 @@ func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *htt
|
||||
patches, status, err := webdav.ReadProppatch(request.Body)
|
||||
this.PanicError(err)
|
||||
|
||||
fmt.Println("status:%v", status)
|
||||
fmt.Printf("status:%v\n", status)
|
||||
|
||||
//prepare a multiStatusWriter.
|
||||
multiStatusWriter := &dav.MultiStatusWriter{Writer: writer}
|
||||
@@ -296,16 +296,16 @@ func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *htt
|
||||
|
||||
}
|
||||
|
||||
//handle download
|
||||
func (this *DavService) HandleGetHeadPost(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
// handle download
|
||||
func (this *DavService) HandleGetHeadPost(writer http.ResponseWriter, request *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("GET %s\n", subPath)
|
||||
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user, space)
|
||||
|
||||
//if this is a Directory, it means Propfind
|
||||
if matter.Dir {
|
||||
this.HandlePropfind(writer, request, user, subPath)
|
||||
this.HandlePropfind(writer, request, user, space, subPath)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -314,8 +314,8 @@ func (this *DavService) HandleGetHeadPost(writer http.ResponseWriter, request *h
|
||||
|
||||
}
|
||||
|
||||
//upload a file
|
||||
func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
// upload a file
|
||||
func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("PUT %s\n", subPath)
|
||||
|
||||
@@ -339,23 +339,23 @@ func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Requ
|
||||
filename := util.GetFilenameOfPath(subPath)
|
||||
dirPath := util.GetDirOfPath(subPath)
|
||||
|
||||
dirMatter := this.matterDao.CheckWithRootByPath(dirPath, user)
|
||||
dirMatter := this.matterDao.CheckWithRootByPath(dirPath, user, space)
|
||||
|
||||
//if exist delete it.
|
||||
srcMatter := this.matterDao.findByUserUuidAndPath(user.Uuid, subPath)
|
||||
if srcMatter != nil {
|
||||
this.matterService.AtomicDelete(request, srcMatter, user)
|
||||
this.matterService.AtomicDelete(request, srcMatter, user, space)
|
||||
}
|
||||
|
||||
this.matterService.Upload(request, request.Body, user, dirMatter, filename, true)
|
||||
this.matterService.Upload(request, request.Body, nil, user, space, dirMatter, filename, true)
|
||||
|
||||
//set the status code 201
|
||||
writer.WriteHeader(http.StatusCreated)
|
||||
|
||||
}
|
||||
|
||||
//delete file
|
||||
func (this *DavService) HandleDelete(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
||||
// delete file
|
||||
func (this *DavService) HandleDelete(w http.ResponseWriter, r *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("DELETE %s\n", subPath)
|
||||
|
||||
@@ -371,13 +371,13 @@ func (this *DavService) HandleDelete(w http.ResponseWriter, r *http.Request, use
|
||||
defer release()
|
||||
}
|
||||
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user, space)
|
||||
|
||||
this.matterService.AtomicDelete(r, matter, user)
|
||||
this.matterService.AtomicDelete(r, matter, user, space)
|
||||
}
|
||||
|
||||
//crate a directory
|
||||
func (this *DavService) HandleMkcol(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
// crate a directory
|
||||
func (this *DavService) HandleMkcol(writer http.ResponseWriter, request *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("MKCOL %s\n", subPath)
|
||||
|
||||
@@ -395,34 +395,34 @@ func (this *DavService) HandleMkcol(writer http.ResponseWriter, request *http.Re
|
||||
thisDirName := util.GetFilenameOfPath(subPath)
|
||||
dirPath := util.GetDirOfPath(subPath)
|
||||
|
||||
dirMatter := this.matterDao.FindWithRootByPath(dirPath, user)
|
||||
dirMatter := this.matterDao.FindWithRootByPath(dirPath, user, space)
|
||||
if dirMatter == nil {
|
||||
//throw conflict error
|
||||
panic(result.CustomWebResult(result.CONFLICT, fmt.Sprintf("%s not exist", dirPath)))
|
||||
}
|
||||
|
||||
//check whether col exists. (RFC2518:8.3.1)
|
||||
dbMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, TRUE, thisDirName)
|
||||
dbMatter := this.matterDao.FindBySpaceNameAndPuuidAndDirAndName(space.Name, dirMatter.Uuid, TRUE, thisDirName)
|
||||
if dbMatter != nil {
|
||||
panic(result.CustomWebResult(result.METHOD_NOT_ALLOWED, fmt.Sprintf("%s already exists", dirPath)))
|
||||
}
|
||||
|
||||
//check whether file exists. (RFC2518:8.3.1)
|
||||
fileMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, FALSE, thisDirName)
|
||||
fileMatter := this.matterDao.FindBySpaceNameAndPuuidAndDirAndName(space.Name, dirMatter.Uuid, FALSE, thisDirName)
|
||||
if fileMatter != nil {
|
||||
panic(result.CustomWebResult(result.METHOD_NOT_ALLOWED, fmt.Sprintf("%s file already exists", dirPath)))
|
||||
}
|
||||
|
||||
this.matterService.AtomicCreateDirectory(request, dirMatter, thisDirName, user)
|
||||
this.matterService.AtomicCreateDirectory(request, dirMatter, thisDirName, user, space)
|
||||
|
||||
}
|
||||
|
||||
//cors options
|
||||
func (this *DavService) HandleOptions(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
||||
// cors options
|
||||
func (this *DavService) HandleOptions(w http.ResponseWriter, r *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("OPTIONS %s\n", subPath)
|
||||
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user)
|
||||
matter := this.matterDao.CheckWithRootByPath(subPath, user, space)
|
||||
|
||||
allow := "OPTIONS, LOCK, PUT, MKCOL"
|
||||
if matter.Dir {
|
||||
@@ -439,11 +439,11 @@ func (this *DavService) HandleOptions(w http.ResponseWriter, r *http.Request, us
|
||||
|
||||
}
|
||||
|
||||
//prepare for moving or copying
|
||||
// prepare for moving or copying
|
||||
func (this *DavService) prepareMoveCopy(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
user *User, subPath string) (
|
||||
user *User, space *Space, subPath string) (
|
||||
srcMatter *Matter,
|
||||
destDirMatter *Matter,
|
||||
srcDirPath string,
|
||||
@@ -506,14 +506,14 @@ func (this *DavService) prepareMoveCopy(
|
||||
}
|
||||
|
||||
//source matter
|
||||
srcMatter = this.matterDao.CheckWithRootByPath(subPath, user)
|
||||
srcMatter = this.matterDao.CheckWithRootByPath(subPath, user, space)
|
||||
|
||||
//if source matter is root.
|
||||
if srcMatter.Uuid == MATTER_ROOT {
|
||||
panic(result.BadRequest("you cannot move the root directory"))
|
||||
}
|
||||
|
||||
destDirMatter = this.matterDao.FindWithRootByPath(destinationDirPath, user)
|
||||
destDirMatter = this.matterDao.FindWithRootByPath(destinationDirPath, user, space)
|
||||
if destDirMatter == nil {
|
||||
//throw conflict error
|
||||
panic(result.CustomWebResult(result.CONFLICT, fmt.Sprintf("%s not exist", destinationDirPath)))
|
||||
@@ -523,8 +523,8 @@ func (this *DavService) prepareMoveCopy(
|
||||
|
||||
}
|
||||
|
||||
//move or rename.
|
||||
func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
// move or rename.
|
||||
func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("MOVE %s\n", subPath)
|
||||
|
||||
@@ -541,14 +541,14 @@ func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Req
|
||||
defer release()
|
||||
}
|
||||
|
||||
srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath)
|
||||
srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, space, subPath)
|
||||
|
||||
//move to the new directory
|
||||
if destinationDirPath == srcDirPath {
|
||||
//if destination path not change. it means rename.
|
||||
this.matterService.AtomicRename(request, srcMatter, destinationName, overwrite, user)
|
||||
this.matterService.AtomicRename(request, srcMatter, destinationName, overwrite, user, space)
|
||||
} else {
|
||||
this.matterService.AtomicMove(request, srcMatter, destDirMatter, overwrite, user)
|
||||
this.matterService.AtomicMove(request, srcMatter, destDirMatter, overwrite, user, space)
|
||||
}
|
||||
|
||||
this.logger.Info("finish moving %s => %s", subPath, destDirMatter.Path)
|
||||
@@ -562,12 +562,12 @@ func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
//copy file/directory
|
||||
func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
// copy file/directory
|
||||
func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
fmt.Printf("COPY %s\n", subPath)
|
||||
|
||||
srcMatter, destDirMatter, _, _, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath)
|
||||
srcMatter, destDirMatter, _, _, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, space, subPath)
|
||||
|
||||
// handle the lock feature.
|
||||
release, status, err := this.confirmLocks(request, destDirMatter.Path+"/"+destinationName, "")
|
||||
@@ -579,7 +579,7 @@ func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Req
|
||||
}
|
||||
|
||||
//copy to the new directory
|
||||
this.matterService.AtomicCopy(request, srcMatter, destDirMatter, destinationName, overwrite, user)
|
||||
this.matterService.AtomicCopy(request, srcMatter, destDirMatter, destinationName, overwrite, user, space)
|
||||
|
||||
this.logger.Info("finish copying %s => %s", subPath, destDirMatter.Path)
|
||||
|
||||
@@ -688,7 +688,7 @@ func (h *DavService) confirmLocks(r *http.Request, src, dst string) (release fun
|
||||
return nil, http.StatusLocked, webdav.ErrLocked
|
||||
}
|
||||
|
||||
//lock.
|
||||
// lock.
|
||||
func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
||||
|
||||
duration, err := webdav.ParseTimeout(r.Header.Get("Timeout"))
|
||||
@@ -785,7 +785,7 @@ func (this *DavService) HandleLock(w http.ResponseWriter, r *http.Request, user
|
||||
|
||||
}
|
||||
|
||||
//unlock
|
||||
// unlock
|
||||
func (this *DavService) HandleUnlock(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
|
||||
|
||||
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
|
||||
@@ -810,44 +810,44 @@ func (this *DavService) HandleUnlock(w http.ResponseWriter, r *http.Request, use
|
||||
}
|
||||
}
|
||||
|
||||
//hanle all the request.
|
||||
func (this *DavService) HandleDav(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
|
||||
// hanle all the request.
|
||||
func (this *DavService) HandleDav(writer http.ResponseWriter, request *http.Request, user *User, space *Space, subPath string) {
|
||||
|
||||
method := request.Method
|
||||
if method == "OPTIONS" {
|
||||
|
||||
//cors option
|
||||
this.HandleOptions(writer, request, user, subPath)
|
||||
this.HandleOptions(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "GET" || method == "HEAD" || method == "POST" {
|
||||
|
||||
//get the detail of file. download
|
||||
this.HandleGetHeadPost(writer, request, user, subPath)
|
||||
this.HandleGetHeadPost(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "DELETE" {
|
||||
|
||||
//delete file
|
||||
this.HandleDelete(writer, request, user, subPath)
|
||||
this.HandleDelete(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "PUT" {
|
||||
|
||||
//upload file
|
||||
this.HandlePut(writer, request, user, subPath)
|
||||
this.HandlePut(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "MKCOL" {
|
||||
|
||||
//crate directory
|
||||
this.HandleMkcol(writer, request, user, subPath)
|
||||
this.HandleMkcol(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "COPY" {
|
||||
|
||||
//copy file/directory
|
||||
this.HandleCopy(writer, request, user, subPath)
|
||||
this.HandleCopy(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "MOVE" {
|
||||
|
||||
//move/rename a file or directory
|
||||
this.HandleMove(writer, request, user, subPath)
|
||||
this.HandleMove(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "LOCK" {
|
||||
|
||||
@@ -862,7 +862,7 @@ func (this *DavService) HandleDav(writer http.ResponseWriter, request *http.Requ
|
||||
} else if method == "PROPFIND" {
|
||||
|
||||
//list a directory
|
||||
this.HandlePropfind(writer, request, user, subPath)
|
||||
this.HandlePropfind(writer, request, user, space, subPath)
|
||||
|
||||
} else if method == "PROPPATCH" {
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DownloadToken struct {
|
||||
Base
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36) not null"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36) not null;index:idx_mu"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36) not null;index:idx_download_token_mu"` //index should unique globally for sqlite
|
||||
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
}
|
||||
|
||||
func (this *DownloadToken) TableName() string {
|
||||
return core.TABLE_PREFIX + "download_token"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
@@ -48,14 +48,14 @@ func (this *FootprintDao) Page(page int, pageSize int, userUuid string, sortArra
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Footprint{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var footprints []*Footprint
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&footprints)
|
||||
this.PanicError(db.Error)
|
||||
pager := NewPager(page, pageSize, count, footprints)
|
||||
pager := NewPager(page, pageSize, int(count), footprints)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
package rest
|
||||
|
||||
import "github.com/eyebluecn/tank/code/core"
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* visit record.
|
||||
*/
|
||||
// Footprint /**
|
||||
// Mysql 5.5 only support one CURRENT_TIMESTAMP
|
||||
// so we use 2018-01-01 00:00:00 as default, which is the first release date of EyeblueTank
|
||||
type Footprint struct {
|
||||
Base
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
Host string `json:"host" gorm:"type:varchar(45) not null"`
|
||||
Uri string `json:"uri" gorm:"type:varchar(255) not null"`
|
||||
Params string `json:"params" gorm:"type:text"`
|
||||
Cost int64 `json:"cost" gorm:"type:bigint(20) not null;default:0"`
|
||||
Success bool `json:"success" gorm:"type:tinyint(1) not null;default:0"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *Footprint) TableName() string {
|
||||
return core.TABLE_PREFIX + "footprint"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
Host string `json:"host" gorm:"type:varchar(45) not null"`
|
||||
Uri string `json:"uri" gorm:"type:varchar(255) not null"`
|
||||
Params string `json:"params" gorm:"type:text"`
|
||||
Cost int64 `json:"cost" gorm:"type:bigint(20) not null;default:0"`
|
||||
Success bool `json:"success" gorm:"type:tinyint(1) not null;default:0"`
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -17,7 +17,7 @@ type ImageCacheDao struct {
|
||||
BaseDao
|
||||
}
|
||||
|
||||
//find by uuid. if not found return nil.
|
||||
// find by uuid. if not found return nil.
|
||||
func (this *ImageCacheDao) FindByUuid(uuid string) *ImageCache {
|
||||
var entity = &ImageCache{}
|
||||
db := core.CONTEXT.GetDB().Where("uuid = ?", uuid).First(entity)
|
||||
@@ -31,7 +31,7 @@ func (this *ImageCacheDao) FindByUuid(uuid string) *ImageCache {
|
||||
return entity
|
||||
}
|
||||
|
||||
//find by uuid. if not found panic NotFound error
|
||||
// find by uuid. if not found panic NotFound error
|
||||
func (this *ImageCacheDao) CheckByUuid(uuid string) *ImageCache {
|
||||
entity := this.FindByUuid(uuid)
|
||||
if entity == nil {
|
||||
@@ -67,7 +67,7 @@ func (this *ImageCacheDao) CheckByUuidAndUserUuid(uuid string, userUuid string)
|
||||
|
||||
// Read
|
||||
var imageCache = &ImageCache{}
|
||||
db := core.CONTEXT.GetDB().Where(&ImageCache{Base: Base{Uuid: uuid}, UserUuid: userUuid}).First(imageCache)
|
||||
db := core.CONTEXT.GetDB().Where(&ImageCache{Uuid: uuid, UserUuid: userUuid}).First(imageCache)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return imageCache
|
||||
@@ -101,14 +101,14 @@ func (this *ImageCacheDao) Page(page int, pageSize int, userUuid string, matterU
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&ImageCache{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var imageCaches []*ImageCache
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&imageCaches)
|
||||
this.PanicError(db.Error)
|
||||
pager := NewPager(page, pageSize, count, imageCaches)
|
||||
pager := NewPager(page, pageSize, int(count), imageCaches)
|
||||
|
||||
return pager
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func (this *ImageCacheDao) Save(imageCache *ImageCache) *ImageCache {
|
||||
|
||||
func (this *ImageCacheDao) deleteFileAndDir(imageCache *ImageCache) {
|
||||
|
||||
filePath := GetUserCacheRootDir(imageCache.Username) + imageCache.Path
|
||||
filePath := GetSpaceCacheRootDir(imageCache.Username) + imageCache.Path
|
||||
|
||||
dirPath := filepath.Dir(filePath)
|
||||
|
||||
@@ -152,7 +152,7 @@ func (this *ImageCacheDao) deleteFileAndDir(imageCache *ImageCache) {
|
||||
|
||||
}
|
||||
|
||||
//delete a file from db and disk.
|
||||
// delete a file from db and disk.
|
||||
func (this *ImageCacheDao) Delete(imageCache *ImageCache) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Delete(&imageCache)
|
||||
@@ -162,7 +162,7 @@ func (this *ImageCacheDao) Delete(imageCache *ImageCache) {
|
||||
|
||||
}
|
||||
|
||||
//delete all the cache of a matter.
|
||||
// delete all the cache of a matter.
|
||||
func (this *ImageCacheDao) DeleteByMatterUuid(matterUuid string) {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
@@ -208,7 +208,7 @@ func (this *ImageCacheDao) SizeBetweenTime(startTime time.Time, endTime time.Tim
|
||||
return size
|
||||
}
|
||||
|
||||
//System cleanup.
|
||||
// System cleanup.
|
||||
func (this *ImageCacheDao) Cleanup() {
|
||||
this.logger.Info("[ImageCacheDao]clean up. Delete all ImageCache ")
|
||||
db := core.CONTEXT.GetDB().Where("uuid is not null").Delete(ImageCache{})
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
)
|
||||
import "time"
|
||||
|
||||
/**
|
||||
* image cache.
|
||||
*/
|
||||
type ImageCache struct {
|
||||
Base
|
||||
Name string `json:"name" gorm:"type:varchar(255) not null"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36);index:idx_mu"`
|
||||
MatterName string `json:"matterName" gorm:"type:varchar(255) not null"`
|
||||
Mode string `json:"mode" gorm:"type:varchar(512)"`
|
||||
Md5 string `json:"md5" gorm:"type:varchar(45)"`
|
||||
Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"`
|
||||
Path string `json:"path" gorm:"type:varchar(512)"`
|
||||
Matter *Matter `json:"matter" gorm:"-"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *ImageCache) TableName() string {
|
||||
return core.TABLE_PREFIX + "image_cache"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Name string `json:"name" gorm:"type:varchar(255) not null"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
MatterUuid string `json:"matterUuid" gorm:"type:char(36);index:idx_image_cache_mu"` //index should unique globally.
|
||||
MatterName string `json:"matterName" gorm:"type:varchar(255) not null"`
|
||||
Mode string `json:"mode" gorm:"type:varchar(512)"`
|
||||
Md5 string `json:"md5" gorm:"type:varchar(45)"`
|
||||
Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"`
|
||||
Path string `json:"path" gorm:"type:varchar(512)"`
|
||||
Matter *Matter `json:"matter" gorm:"-"`
|
||||
}
|
||||
|
||||
// get the absolute path. path in db means relative path.
|
||||
func (this *ImageCache) AbsolutePath() string {
|
||||
return GetUserCacheRootDir(this.Username) + this.Path
|
||||
return GetSpaceCacheRootDir(this.Username) + this.Path
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
//@Service
|
||||
// @Service
|
||||
type ImageCacheService struct {
|
||||
BaseBean
|
||||
imageCacheDao *ImageCacheDao
|
||||
@@ -92,7 +92,7 @@ func (this *ImageCacheService) ResizeParams(request *http.Request) (needProcess
|
||||
|
||||
}
|
||||
|
||||
//resize image.
|
||||
// resize image.
|
||||
func (this *ImageCacheService) ResizeImage(request *http.Request, filePath string) *image.NRGBA {
|
||||
|
||||
diskFile, err := os.Open(filePath)
|
||||
@@ -148,7 +148,7 @@ func (this *ImageCacheService) ResizeImage(request *http.Request, filePath strin
|
||||
}
|
||||
}
|
||||
|
||||
//cache an image
|
||||
// cache an image
|
||||
func (this *ImageCacheService) cacheImage(writer http.ResponseWriter, request *http.Request, matter *Matter) *ImageCache {
|
||||
|
||||
//only these image can do.
|
||||
@@ -177,7 +177,7 @@ func (this *ImageCacheService) cacheImage(writer http.ResponseWriter, request *h
|
||||
|
||||
cacheImageName := util.GetSimpleFileName(matter.Name) + "_" + mode + extension
|
||||
cacheImageRelativePath := util.GetSimpleFileName(matter.Path) + "_" + mode + extension
|
||||
cacheImageAbsolutePath := GetUserCacheRootDir(user.Username) + util.GetSimpleFileName(matter.Path) + "_" + mode + extension
|
||||
cacheImageAbsolutePath := GetSpaceCacheRootDir(user.Username) + util.GetSimpleFileName(matter.Path) + "_" + mode + extension
|
||||
|
||||
//create directory
|
||||
dir := filepath.Dir(cacheImageAbsolutePath)
|
||||
|
||||
@@ -6,16 +6,24 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/third"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
//install apis. Only when installing period can be visited.
|
||||
// install apis. Only when installing period can be visited.
|
||||
type InstallController struct {
|
||||
BaseController
|
||||
uploadTokenDao *UploadTokenDao
|
||||
@@ -24,7 +32,7 @@ type InstallController struct {
|
||||
matterService *MatterService
|
||||
imageCacheDao *ImageCacheDao
|
||||
imageCacheService *ImageCacheService
|
||||
tableNames []IBase
|
||||
tableNames []interface{}
|
||||
}
|
||||
|
||||
func (this *InstallController) Init() {
|
||||
@@ -60,7 +68,7 @@ func (this *InstallController) Init() {
|
||||
this.imageCacheService = c
|
||||
}
|
||||
|
||||
this.tableNames = []IBase{
|
||||
this.tableNames = []interface{}{
|
||||
&Dashboard{},
|
||||
&Bridge{},
|
||||
&DownloadToken{},
|
||||
@@ -70,7 +78,11 @@ func (this *InstallController) Init() {
|
||||
&Preference{},
|
||||
&Session{},
|
||||
&Share{},
|
||||
&Space{},
|
||||
&SpaceMember{},
|
||||
&UploadToken{},
|
||||
&UploadSession{},
|
||||
&UploadChunk{},
|
||||
&User{},
|
||||
}
|
||||
|
||||
@@ -92,6 +104,7 @@ func (this *InstallController) RegisterRoutes() map[string]func(writer http.Resp
|
||||
}
|
||||
|
||||
func (this *InstallController) openDbConnection(writer http.ResponseWriter, request *http.Request) *gorm.DB {
|
||||
dbType := request.FormValue("dbType")
|
||||
mysqlPortStr := request.FormValue("mysqlPort")
|
||||
mysqlHost := request.FormValue("mysqlHost")
|
||||
mysqlSchema := request.FormValue("mysqlSchema")
|
||||
@@ -106,55 +119,114 @@ func (this *InstallController) openDbConnection(writer http.ResponseWriter, requ
|
||||
mysqlPort = tmp
|
||||
}
|
||||
|
||||
mysqlUrl := util.GetMysqlUrl(mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword, mysqlCharset)
|
||||
//log config
|
||||
dbLogger := logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // slow SQL 1s
|
||||
LogLevel: logger.Silent, // log level. open when debug.
|
||||
IgnoreRecordNotFoundError: true, // ignore ErrRecordNotFound
|
||||
Colorful: false, // colorful print
|
||||
},
|
||||
)
|
||||
//table name strategy
|
||||
namingStrategy := core.CONFIG.NamingStrategy()
|
||||
|
||||
this.logger.Info("Connect MySQL %s", mysqlUrl)
|
||||
if dbType == "sqlite" {
|
||||
|
||||
var err error = nil
|
||||
db, err := gorm.Open("mysql", mysqlUrl)
|
||||
this.PanicError(err)
|
||||
var err error = nil
|
||||
sqliteFolder := core.CONFIG.SqliteFolder() + "/tank.sqlite"
|
||||
this.logger.Info("Connect Sqlite %s", sqliteFolder)
|
||||
db, err := gorm.Open(sqlite.Open(sqliteFolder), &gorm.Config{Logger: dbLogger, NamingStrategy: namingStrategy})
|
||||
|
||||
db.LogMode(false)
|
||||
if err != nil {
|
||||
core.LOGGER.Panic("failed to connect sqlite database")
|
||||
}
|
||||
|
||||
return db
|
||||
//sqlite lock issue. https://gist.github.com/mrnugget/0eda3b2b53a70fa4a894
|
||||
phyDb, err := db.DB()
|
||||
phyDb.SetMaxOpenConns(1)
|
||||
|
||||
return db
|
||||
|
||||
} else {
|
||||
mysqlUrl := util.GetMysqlUrl(mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword, mysqlCharset)
|
||||
|
||||
this.logger.Info("Connect MySQL %s", mysqlUrl)
|
||||
|
||||
var err error = nil
|
||||
db, err := gorm.Open(mysql.Open(mysqlUrl), &gorm.Config{Logger: dbLogger, NamingStrategy: namingStrategy})
|
||||
this.PanicError(err)
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *InstallController) closeDbConnection(db *gorm.DB) {
|
||||
|
||||
if db != nil {
|
||||
err := db.Close()
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
this.logger.Error("occur error when close db. %v", err)
|
||||
core.LOGGER.Error("occur error when get *sql.DB %s", err.Error())
|
||||
}
|
||||
err = sqlDB.Close()
|
||||
if err != nil {
|
||||
core.LOGGER.Error("occur error when closing db %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *InstallController) getTableMeta(gormDb *gorm.DB, entity IBase) (bool, []*gorm.StructField, []*gorm.StructField) {
|
||||
// (tableName, exists, allFields, missingFields)
|
||||
func (this *InstallController) getTableMeta(gormDb *gorm.DB, entity interface{}) (string, bool, []*InstallFieldInfo, []*InstallFieldInfo) {
|
||||
|
||||
db := gormDb.Unscoped()
|
||||
scope := db.NewScope(entity)
|
||||
//get all useful fields from model.
|
||||
entitySchema, err := schema.Parse(entity, &sync.Map{}, core.CONFIG.NamingStrategy())
|
||||
this.PanicError(err)
|
||||
|
||||
tableName := scope.TableName()
|
||||
modelStruct := scope.GetModelStruct()
|
||||
allFields := modelStruct.StructFields
|
||||
var missingFields = make([]*gorm.StructField, 0)
|
||||
tableName := entitySchema.Table
|
||||
|
||||
if !scope.Dialect().HasTable(tableName) {
|
||||
var allFields = make([]*InstallFieldInfo, 0)
|
||||
|
||||
for _, field := range entitySchema.Fields {
|
||||
allFields = append(allFields, &InstallFieldInfo{
|
||||
Name: field.DBName,
|
||||
DataType: string(field.DataType),
|
||||
})
|
||||
}
|
||||
|
||||
var missingFields = make([]*InstallFieldInfo, 0)
|
||||
|
||||
if !gormDb.Migrator().HasTable(tableName) {
|
||||
missingFields = append(missingFields, allFields...)
|
||||
|
||||
return false, allFields, missingFields
|
||||
return tableName, false, allFields, missingFields
|
||||
} else {
|
||||
|
||||
for _, field := range allFields {
|
||||
if !scope.Dialect().HasColumn(tableName, field.DBName) {
|
||||
if field.IsNormal {
|
||||
missingFields = append(missingFields, field)
|
||||
//tag with `gorm:"-"` will be ""
|
||||
if field.Name != "" {
|
||||
|
||||
database := gormDb.Migrator().CurrentDatabase()
|
||||
|
||||
//if sqlite
|
||||
if _, ok := gormDb.Dialector.(*sqlite.Dialector); ok {
|
||||
|
||||
if !gormDb.Migrator().HasColumn(tableName, field.Name) {
|
||||
missingFields = append(missingFields, field)
|
||||
}
|
||||
} else {
|
||||
|
||||
if !third.MysqlMigratorHasColumn(gormDb, database, tableName, field.Name) {
|
||||
missingFields = append(missingFields, field)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true, allFields, missingFields
|
||||
return tableName, true, allFields, missingFields
|
||||
}
|
||||
|
||||
}
|
||||
@@ -164,9 +236,10 @@ func (this *InstallController) getTableMetaList(db *gorm.DB) []*InstallTableInfo
|
||||
var installTableInfos []*InstallTableInfo
|
||||
|
||||
for _, iBase := range this.tableNames {
|
||||
exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
tableName, exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
|
||||
installTableInfos = append(installTableInfos, &InstallTableInfo{
|
||||
Name: iBase.TableName(),
|
||||
Name: tableName,
|
||||
TableExist: exist,
|
||||
AllFields: allFields,
|
||||
MissingFields: missingFields,
|
||||
@@ -185,7 +258,7 @@ func (this *InstallController) validateTableMetaList(tableInfoList []*InstallTab
|
||||
|
||||
var strs []string
|
||||
for _, v := range tableInfo.MissingFields {
|
||||
strs = append(strs, v.DBName)
|
||||
strs = append(strs, v.Name)
|
||||
}
|
||||
|
||||
panic(result.BadRequest(fmt.Sprintf("table %s miss the following fields %v", tableInfo.Name, strs)))
|
||||
@@ -197,14 +270,16 @@ func (this *InstallController) validateTableMetaList(tableInfoList []*InstallTab
|
||||
|
||||
}
|
||||
|
||||
//Ping db.
|
||||
// Ping db.
|
||||
func (this *InstallController) Verify(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
db := this.openDbConnection(writer, request)
|
||||
defer this.closeDbConnection(db)
|
||||
|
||||
this.logger.Info("Ping DB")
|
||||
err := db.DB().Ping()
|
||||
phyDb, err := db.DB()
|
||||
this.PanicError(err)
|
||||
err = phyDb.Ping()
|
||||
this.PanicError(err)
|
||||
|
||||
return this.Success("OK")
|
||||
@@ -227,13 +302,21 @@ func (this *InstallController) CreateTable(writer http.ResponseWriter, request *
|
||||
|
||||
for _, iBase := range this.tableNames {
|
||||
|
||||
//complete the missing fields or create table. use utf8 charset
|
||||
db1 := db.Set("gorm:table_options", "CHARSET=utf8mb4").AutoMigrate(iBase)
|
||||
this.PanicError(db1.Error)
|
||||
if _, ok := db.Dialector.(*sqlite.Dialector); ok {
|
||||
//if sqlite. no need to set CHARSET.
|
||||
err := db.AutoMigrate(iBase)
|
||||
this.PanicError(err)
|
||||
} else {
|
||||
//use utf8 charset
|
||||
mysqlCharset := request.FormValue("mysqlCharset")
|
||||
err := db.Set("gorm:table_options", fmt.Sprintf("CHARSET=%s", mysqlCharset)).AutoMigrate(iBase)
|
||||
this.PanicError(err)
|
||||
}
|
||||
|
||||
exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
//complete the missing fields or create table.
|
||||
tableName, exist, allFields, missingFields := this.getTableMeta(db, iBase)
|
||||
installTableInfos = append(installTableInfos, &InstallTableInfo{
|
||||
Name: iBase.TableName(),
|
||||
Name: tableName,
|
||||
TableExist: exist,
|
||||
AllFields: allFields,
|
||||
MissingFields: missingFields,
|
||||
@@ -245,7 +328,7 @@ func (this *InstallController) CreateTable(writer http.ResponseWriter, request *
|
||||
|
||||
}
|
||||
|
||||
//get the list of admin.
|
||||
// get the list of admin.
|
||||
func (this *InstallController) AdminList(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
db := this.openDbConnection(writer, request)
|
||||
@@ -263,7 +346,7 @@ func (this *InstallController) AdminList(writer http.ResponseWriter, request *ht
|
||||
return this.Success(users)
|
||||
}
|
||||
|
||||
//create admin
|
||||
// create admin
|
||||
func (this *InstallController) CreateAdmin(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
db := this.openDbConnection(writer, request)
|
||||
@@ -289,9 +372,21 @@ func (this *InstallController) CreateAdmin(writer http.ResponseWriter, request *
|
||||
panic(result.BadRequestI18n(request, i18n.UsernameExist, adminUsername))
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
space := &Space{}
|
||||
timeUUID, _ := uuid.NewV4()
|
||||
user.Uuid = string(timeUUID.String())
|
||||
space.Uuid = timeUUID.String()
|
||||
space.CreateTime = time.Now()
|
||||
space.UpdateTime = time.Now()
|
||||
space.Name = adminUsername
|
||||
space.UserUuid = ""
|
||||
space.SizeLimit = -1
|
||||
space.Type = SPACE_TYPE_PRIVATE
|
||||
db3 := db.Create(space)
|
||||
this.PanicError(db3.Error)
|
||||
|
||||
user := &User{}
|
||||
timeUUID, _ = uuid.NewV4()
|
||||
user.Uuid = timeUUID.String()
|
||||
user.CreateTime = time.Now()
|
||||
user.UpdateTime = time.Now()
|
||||
user.LastTime = time.Now()
|
||||
@@ -299,17 +394,21 @@ func (this *InstallController) CreateAdmin(writer http.ResponseWriter, request *
|
||||
user.Role = USER_ROLE_ADMINISTRATOR
|
||||
user.Username = adminUsername
|
||||
user.Password = util.GetBcrypt(adminPassword)
|
||||
user.SizeLimit = -1
|
||||
user.SpaceUuid = space.Uuid
|
||||
user.Status = USER_STATUS_OK
|
||||
|
||||
db3 := db.Create(user)
|
||||
this.PanicError(db3.Error)
|
||||
db4 := db.Create(user)
|
||||
this.PanicError(db4.Error)
|
||||
|
||||
space.UserUuid = user.Uuid
|
||||
db5 := db.Save(space)
|
||||
this.PanicError(db5.Error)
|
||||
|
||||
return this.Success("OK")
|
||||
|
||||
}
|
||||
|
||||
//(if there is admin in db)Validate admin.
|
||||
// (if there is admin in db)Validate admin.
|
||||
func (this *InstallController) ValidateAdmin(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
db := this.openDbConnection(writer, request)
|
||||
@@ -343,9 +442,10 @@ func (this *InstallController) ValidateAdmin(writer http.ResponseWriter, request
|
||||
|
||||
}
|
||||
|
||||
//Finish the installation
|
||||
// Finish the installation
|
||||
func (this *InstallController) Finish(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
dbType := request.FormValue("dbType")
|
||||
mysqlPortStr := request.FormValue("mysqlPort")
|
||||
mysqlHost := request.FormValue("mysqlHost")
|
||||
mysqlSchema := request.FormValue("mysqlSchema")
|
||||
@@ -377,7 +477,7 @@ func (this *InstallController) Finish(writer http.ResponseWriter, request *http.
|
||||
}
|
||||
|
||||
//announce the config to write config to tank.json
|
||||
core.CONFIG.FinishInstall(mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword, mysqlCharset)
|
||||
core.CONFIG.FinishInstall(dbType, mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword, mysqlCharset)
|
||||
|
||||
//announce the context to broadcast the installation news to bean.
|
||||
core.CONTEXT.InstallOk()
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package rest
|
||||
|
||||
import "github.com/jinzhu/gorm"
|
||||
// InstallTableInfo /**
|
||||
// table meta info.
|
||||
type InstallTableInfo struct {
|
||||
Name string `json:"name"`
|
||||
TableExist bool `json:"tableExist"`
|
||||
AllFields []*InstallFieldInfo `json:"allFields"`
|
||||
MissingFields []*InstallFieldInfo `json:"missingFields"`
|
||||
}
|
||||
|
||||
/**
|
||||
* table meta info.
|
||||
*/
|
||||
type InstallTableInfo struct {
|
||||
Name string `json:"name"`
|
||||
TableExist bool `json:"tableExist"`
|
||||
AllFields []*gorm.StructField `json:"allFields"`
|
||||
MissingFields []*gorm.StructField `json:"missingFields"`
|
||||
type InstallFieldInfo struct {
|
||||
Name string `json:"name"`
|
||||
DataType string `json:"dataType"`
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ type MatterController struct {
|
||||
downloadTokenDao *DownloadTokenDao
|
||||
imageCacheDao *ImageCacheDao
|
||||
shareDao *ShareDao
|
||||
spaceDao *SpaceDao
|
||||
shareService *ShareService
|
||||
bridgeDao *BridgeDao
|
||||
imageCacheService *ImageCacheService
|
||||
@@ -51,6 +52,11 @@ func (this *MatterController) Init() {
|
||||
this.shareDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.shareService)
|
||||
if b, ok := b.(*ShareService); ok {
|
||||
this.shareService = b
|
||||
@@ -70,6 +76,9 @@ func (this *MatterController) Init() {
|
||||
func (this *MatterController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
|
||||
routeMap["/api/matter/detail"] = this.Wrap(this.Detail, USER_ROLE_USER)
|
||||
routeMap["/api/matter/page"] = this.Wrap(this.Page, USER_ROLE_USER)
|
||||
routeMap["/api/matter/search"] = this.Wrap(this.Search, USER_ROLE_USER)
|
||||
|
||||
routeMap["/api/matter/create/directory"] = this.Wrap(this.CreateDirectory, USER_ROLE_USER)
|
||||
routeMap["/api/matter/upload"] = this.Wrap(this.Upload, USER_ROLE_USER)
|
||||
@@ -84,8 +93,6 @@ func (this *MatterController) RegisterRoutes() map[string]func(writer http.Respo
|
||||
routeMap["/api/matter/rename"] = this.Wrap(this.Rename, USER_ROLE_USER)
|
||||
routeMap["/api/matter/change/privacy"] = this.Wrap(this.ChangePrivacy, USER_ROLE_USER)
|
||||
routeMap["/api/matter/move"] = this.Wrap(this.Move, USER_ROLE_USER)
|
||||
routeMap["/api/matter/detail"] = this.Wrap(this.Detail, USER_ROLE_USER)
|
||||
routeMap["/api/matter/page"] = this.Wrap(this.Page, USER_ROLE_GUEST)
|
||||
|
||||
//mirror local files.
|
||||
routeMap["/api/matter/mirror"] = this.Wrap(this.Mirror, USER_ROLE_USER)
|
||||
@@ -96,145 +103,150 @@ func (this *MatterController) RegisterRoutes() map[string]func(writer http.Respo
|
||||
|
||||
func (this *MatterController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
if uuid == "" {
|
||||
panic(result.BadRequest("uuid cannot be null"))
|
||||
}
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckReadableByUuid(request, user, spaceUuid)
|
||||
|
||||
matter := this.matterService.Detail(request, uuid)
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
//add the user's info.
|
||||
if space.Uuid == user.SpaceUuid {
|
||||
matter.User = user
|
||||
} else {
|
||||
matter.User = this.userDao.FindByUuid(matter.UserUuid)
|
||||
}
|
||||
|
||||
return this.Success(matter)
|
||||
|
||||
}
|
||||
|
||||
func (this *MatterController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
pageStr := request.FormValue("page")
|
||||
pageSizeStr := request.FormValue("pageSize")
|
||||
orderCreateTime := request.FormValue("orderCreateTime")
|
||||
orderUpdateTime := request.FormValue("orderUpdateTime")
|
||||
orderDeleteTime := request.FormValue("orderDeleteTime")
|
||||
orderSort := request.FormValue("orderSort")
|
||||
orderTimes := request.FormValue("orderTimes")
|
||||
page := util.ExtractRequestOptionalInt(request, "page", 0)
|
||||
pageSize := util.ExtractRequestOptionalInt(request, "pageSize", 200)
|
||||
orderCreateTime := util.ExtractRequestOptionalString(request, "orderCreateTime", "")
|
||||
orderUpdateTime := util.ExtractRequestOptionalString(request, "orderUpdateTime", "")
|
||||
orderDeleteTime := util.ExtractRequestOptionalString(request, "orderDeleteTime", "")
|
||||
orderSort := util.ExtractRequestOptionalString(request, "orderSort", "")
|
||||
orderTimes := util.ExtractRequestOptionalString(request, "orderTimes", "")
|
||||
orderDir := util.ExtractRequestOptionalString(request, "orderDir", "")
|
||||
orderSize := util.ExtractRequestOptionalString(request, "orderSize", "")
|
||||
orderName := util.ExtractRequestOptionalString(request, "orderName", "")
|
||||
|
||||
puuid := request.FormValue("puuid")
|
||||
name := request.FormValue("name")
|
||||
dir := request.FormValue("dir")
|
||||
deleted := request.FormValue("deleted")
|
||||
orderDir := request.FormValue("orderDir")
|
||||
orderSize := request.FormValue("orderSize")
|
||||
orderName := request.FormValue("orderName")
|
||||
extensionsStr := request.FormValue("extensions")
|
||||
puuid := util.ExtractRequestOptionalString(request, "puuid", "")
|
||||
name := util.ExtractRequestOptionalString(request, "name", "")
|
||||
dir := util.ExtractRequestOptionalString(request, "dir", "")
|
||||
deleted := util.ExtractRequestOptionalString(request, "deleted", "")
|
||||
extensionsStr := util.ExtractRequestOptionalString(request, "extensions", "")
|
||||
|
||||
var userUuid string
|
||||
|
||||
//auth by shareUuid.
|
||||
shareUuid := request.FormValue("shareUuid")
|
||||
shareCode := request.FormValue("shareCode")
|
||||
shareRootUuid := request.FormValue("shareRootUuid")
|
||||
if shareUuid != "" {
|
||||
|
||||
if puuid == "" {
|
||||
panic(result.BadRequest("puuid cannot be null"))
|
||||
}
|
||||
|
||||
dirMatter := this.matterDao.CheckByUuid(puuid)
|
||||
if !dirMatter.Dir {
|
||||
panic(result.BadRequest("puuid is not a directory"))
|
||||
}
|
||||
|
||||
user := this.findUser(request)
|
||||
|
||||
this.shareService.ValidateMatter(request, shareUuid, shareCode, user, shareRootUuid, dirMatter)
|
||||
puuid = dirMatter.Uuid
|
||||
|
||||
} else {
|
||||
//if cannot auth by share. Then login is required.
|
||||
user := this.checkUser(request)
|
||||
userUuid = user.Uuid
|
||||
|
||||
}
|
||||
|
||||
var page int
|
||||
if pageStr != "" {
|
||||
page, _ = strconv.Atoi(pageStr)
|
||||
}
|
||||
|
||||
pageSize := 200
|
||||
if pageSizeStr != "" {
|
||||
tmp, err := strconv.Atoi(pageSizeStr)
|
||||
if err == nil {
|
||||
pageSize = tmp
|
||||
}
|
||||
}
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
this.spaceService.CheckReadableByUuid(request, user, spaceUuid)
|
||||
|
||||
var extensions []string
|
||||
if extensionsStr != "" {
|
||||
extensions = strings.Split(extensionsStr, ",")
|
||||
}
|
||||
|
||||
sortArray := []builder.OrderPair{
|
||||
{
|
||||
Key: "dir",
|
||||
Value: orderDir,
|
||||
},
|
||||
{
|
||||
Key: "create_time",
|
||||
Value: orderCreateTime,
|
||||
},
|
||||
{
|
||||
Key: "update_time",
|
||||
Value: orderUpdateTime,
|
||||
},
|
||||
{
|
||||
Key: "delete_time",
|
||||
Value: orderDeleteTime,
|
||||
},
|
||||
{
|
||||
Key: "sort",
|
||||
Value: orderSort,
|
||||
},
|
||||
{
|
||||
Key: "size",
|
||||
Value: orderSize,
|
||||
},
|
||||
{
|
||||
Key: "name",
|
||||
Value: orderName,
|
||||
},
|
||||
{
|
||||
Key: "times",
|
||||
Value: orderTimes,
|
||||
},
|
||||
}
|
||||
|
||||
pager := this.matterDao.Page(page, pageSize, puuid, userUuid, name, dir, deleted, extensions, sortArray)
|
||||
pager := this.matterService.Page(
|
||||
request,
|
||||
page,
|
||||
pageSize,
|
||||
orderCreateTime,
|
||||
orderUpdateTime,
|
||||
orderDeleteTime,
|
||||
orderSort,
|
||||
orderTimes,
|
||||
orderDir,
|
||||
orderSize,
|
||||
orderName,
|
||||
puuid,
|
||||
name,
|
||||
dir,
|
||||
deleted,
|
||||
extensions,
|
||||
spaceUuid,
|
||||
)
|
||||
|
||||
return this.Success(pager)
|
||||
}
|
||||
|
||||
func (this *MatterController) CreateDirectory(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
// DFS search.
|
||||
func (this *MatterController) Search(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
puuid := request.FormValue("puuid")
|
||||
name := request.FormValue("name")
|
||||
limit := util.ExtractRequestOptionalInt(request, "limit", 200)
|
||||
puuid := util.ExtractRequestString(request, "puuid")
|
||||
keyword := util.ExtractRequestString(request, "keyword")
|
||||
deletedStr := util.ExtractRequestOptionalString(request, "deleted", "")
|
||||
var deleted = false
|
||||
if deletedStr == TRUE {
|
||||
deleted = true
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
this.spaceService.CheckReadableByUuid(request, user, spaceUuid)
|
||||
|
||||
var dirMatter = this.matterDao.CheckWithRootByUuid(puuid, user)
|
||||
matters := this.matterService.DfsSearch(
|
||||
request,
|
||||
limit,
|
||||
puuid,
|
||||
keyword,
|
||||
spaceUuid,
|
||||
deleted,
|
||||
)
|
||||
|
||||
matter := this.matterService.AtomicCreateDirectory(request, dirMatter, name, user)
|
||||
//sort.
|
||||
//从小到大自定义排序
|
||||
sort.Slice(matters, func(i, j int) bool {
|
||||
matter1 := matters[i]
|
||||
matter2 := matters[j]
|
||||
if matter1.Dir {
|
||||
return true
|
||||
} else if matter2.Dir {
|
||||
return false
|
||||
} else {
|
||||
cmp := strings.Compare(matter1.Name, matter2.Name)
|
||||
if cmp < 0 {
|
||||
return true
|
||||
} else if cmp == 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return this.Success(matters)
|
||||
}
|
||||
|
||||
func (this *MatterController) CreateDirectory(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
puuid := util.ExtractRequestString(request, "puuid")
|
||||
name := util.ExtractRequestString(request, "name")
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
var dirMatter = this.matterDao.CheckWithRootByUuid(puuid, space)
|
||||
|
||||
matter := this.matterService.AtomicCreateDirectory(request, dirMatter, name, user, space)
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
func (this *MatterController) Upload(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
puuid := util.ExtractRequestString(request, "puuid")
|
||||
privacy := util.ExtractRequestOptionalBool(request, "privacy", true)
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
puuid := request.FormValue("puuid")
|
||||
privacyStr := request.FormValue("privacy")
|
||||
file, handler, err := request.FormFile("file")
|
||||
this.PanicError(err)
|
||||
defer func() {
|
||||
@@ -242,14 +254,10 @@ func (this *MatterController) Upload(writer http.ResponseWriter, request *http.R
|
||||
this.PanicError(err)
|
||||
}()
|
||||
|
||||
user := this.checkUser(request)
|
||||
|
||||
privacy := privacyStr == TRUE
|
||||
|
||||
err = request.ParseMultipartForm(32 << 20)
|
||||
this.PanicError(err)
|
||||
|
||||
//for IE browser. filename may contains filepath.
|
||||
//for IE browser. filename may contain filepath.
|
||||
fileName := handler.Filename
|
||||
pos := strings.LastIndex(fileName, "\\")
|
||||
if pos != -1 {
|
||||
@@ -260,65 +268,77 @@ func (this *MatterController) Upload(writer http.ResponseWriter, request *http.R
|
||||
fileName = fileName[pos+1:]
|
||||
}
|
||||
|
||||
dirMatter := this.matterDao.CheckWithRootByUuid(puuid, user)
|
||||
dirMatter := this.matterDao.CheckWithRootByUuid(puuid, space)
|
||||
|
||||
//support upload simultaneously
|
||||
matter := this.matterService.Upload(request, file, user, dirMatter, fileName, privacy)
|
||||
matter := this.matterService.Upload(request, file, handler, user, space, dirMatter, fileName, privacy)
|
||||
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
//crawl a file by url.
|
||||
// crawl a file by url.
|
||||
func (this *MatterController) Crawl(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
url := request.FormValue("url")
|
||||
destPath := request.FormValue("destPath")
|
||||
filename := request.FormValue("filename")
|
||||
url := util.ExtractRequestString(request, "url")
|
||||
destPath := util.ExtractRequestOptionalString(request, "destPath", "")
|
||||
puuid := util.ExtractRequestOptionalString(request, "puuid", "")
|
||||
filename := util.ExtractRequestString(request, "filename")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
dirMatter := this.matterService.CreateDirectories(request, user, destPath)
|
||||
var dirMatter *Matter
|
||||
if puuid != "" {
|
||||
dirMatter = this.matterDao.CheckWithRootByUuid(puuid, space)
|
||||
if dirMatter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
if !dirMatter.Dir {
|
||||
panic(" puuid is not a dir.")
|
||||
}
|
||||
} else {
|
||||
if destPath == "" {
|
||||
panic(" puuid or destPath cannot be null")
|
||||
}
|
||||
dirMatter = this.matterService.CreateDirectories(request, user, space, destPath)
|
||||
}
|
||||
|
||||
if url == "" || (!strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://")) {
|
||||
panic(" url must start with http:// or https://")
|
||||
}
|
||||
|
||||
if filename == "" {
|
||||
panic("filename cannot be null")
|
||||
}
|
||||
|
||||
matter := this.matterService.AtomicCrawl(request, url, filename, user, dirMatter, true)
|
||||
matter := this.matterService.AtomicCrawl(request, url, filename, user, space, dirMatter, true)
|
||||
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
//soft delete.
|
||||
// soft delete.
|
||||
func (this *MatterController) SoftDelete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
if uuid == "" {
|
||||
panic(result.BadRequest("uuid cannot be null"))
|
||||
}
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicSoftDelete(request, matter, user)
|
||||
this.matterService.AtomicSoftDelete(request, matter, user, space)
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
func (this *MatterController) SoftDeleteBatch(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuids := request.FormValue("uuids")
|
||||
if uuids == "" {
|
||||
panic(result.BadRequest("uuids cannot be null"))
|
||||
}
|
||||
uuids := util.ExtractRequestString(request, "uuids")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
uuidArray := strings.Split(uuids, ",")
|
||||
|
||||
@@ -332,7 +352,7 @@ func (this *MatterController) SoftDeleteBatch(writer http.ResponseWriter, reques
|
||||
continue
|
||||
}
|
||||
|
||||
if matter.UserUuid != user.Uuid {
|
||||
if matter.SpaceUuid != spaceUuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
@@ -340,24 +360,24 @@ func (this *MatterController) SoftDeleteBatch(writer http.ResponseWriter, reques
|
||||
}
|
||||
|
||||
for _, matter := range matters {
|
||||
this.matterService.AtomicSoftDelete(request, matter, user)
|
||||
this.matterService.AtomicSoftDelete(request, matter, user, space)
|
||||
}
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//recovery delete.
|
||||
// recovery delete.
|
||||
func (this *MatterController) Recovery(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
if uuid == "" {
|
||||
panic(result.BadRequest("uuid cannot be null"))
|
||||
}
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
@@ -366,13 +386,13 @@ func (this *MatterController) Recovery(writer http.ResponseWriter, request *http
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//recovery batch.
|
||||
// recovery batch.
|
||||
func (this *MatterController) RecoveryBatch(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
uuids := util.ExtractRequestString(request, "uuids")
|
||||
|
||||
uuids := request.FormValue("uuids")
|
||||
if uuids == "" {
|
||||
panic(result.BadRequest("uuids cannot be null"))
|
||||
}
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
uuidArray := strings.Split(uuids, ",")
|
||||
|
||||
@@ -385,8 +405,7 @@ func (this *MatterController) RecoveryBatch(writer http.ResponseWriter, request
|
||||
continue
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
@@ -397,35 +416,34 @@ func (this *MatterController) RecoveryBatch(writer http.ResponseWriter, request
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//complete delete.
|
||||
// complete delete.
|
||||
func (this *MatterController) Delete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
if uuid == "" {
|
||||
panic(result.BadRequest("uuid cannot be null"))
|
||||
}
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicDelete(request, matter, user)
|
||||
this.matterService.AtomicDelete(request, matter, user, space)
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
func (this *MatterController) DeleteBatch(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuids := request.FormValue("uuids")
|
||||
if uuids == "" {
|
||||
panic(result.BadRequest("uuids cannot be null"))
|
||||
}
|
||||
uuids := util.ExtractRequestString(request, "uuids")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
uuidArray := strings.Split(uuids, ",")
|
||||
user := this.checkUser(request)
|
||||
matters := make([]*Matter, 0)
|
||||
for _, uuid := range uuidArray {
|
||||
|
||||
@@ -436,7 +454,7 @@ func (this *MatterController) DeleteBatch(writer http.ResponseWriter, request *h
|
||||
continue
|
||||
}
|
||||
|
||||
if matter.UserUuid != user.Uuid {
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
@@ -445,13 +463,13 @@ func (this *MatterController) DeleteBatch(writer http.ResponseWriter, request *h
|
||||
|
||||
for _, matter := range matters {
|
||||
|
||||
this.matterService.AtomicDelete(request, matter, user)
|
||||
this.matterService.AtomicDelete(request, matter, user, space)
|
||||
}
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//manual clean expired deleted matters.
|
||||
// manual clean expired deleted matters.
|
||||
func (this *MatterController) CleanExpiredDeletedMatters(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
this.matterService.CleanExpiredDeletedMatters()
|
||||
@@ -461,29 +479,32 @@ func (this *MatterController) CleanExpiredDeletedMatters(writer http.ResponseWri
|
||||
|
||||
func (this *MatterController) Rename(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
name := request.FormValue("name")
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
name := util.ExtractRequestString(request, "name")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
|
||||
if matter.UserUuid != user.Uuid {
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.matterService.AtomicRename(request, matter, name, false, user)
|
||||
this.matterService.AtomicRename(request, matter, name, false, user, space)
|
||||
|
||||
return this.Success(matter)
|
||||
}
|
||||
|
||||
func (this *MatterController) ChangePrivacy(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
uuid := request.FormValue("uuid")
|
||||
privacyStr := request.FormValue("privacy")
|
||||
privacy := false
|
||||
if privacyStr == TRUE {
|
||||
privacy = true
|
||||
}
|
||||
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
privacy := util.ExtractRequestOptionalBool(request, "privacy", false)
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
matter := this.matterDao.CheckByUuid(uuid)
|
||||
|
||||
@@ -495,8 +516,7 @@ func (this *MatterController) ChangePrivacy(writer http.ResponseWriter, request
|
||||
panic(result.BadRequest("not changed. Invalid operation."))
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
if matter.UserUuid != user.Uuid {
|
||||
if matter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
@@ -507,25 +527,21 @@ func (this *MatterController) ChangePrivacy(writer http.ResponseWriter, request
|
||||
}
|
||||
|
||||
func (this *MatterController) Move(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
srcUuidsStr := request.FormValue("srcUuids")
|
||||
destUuid := request.FormValue("destUuid")
|
||||
srcUuidsStr := util.ExtractRequestString(request, "srcUuids")
|
||||
destUuid := util.ExtractRequestString(request, "destUuid")
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
var srcUuids []string
|
||||
if srcUuidsStr == "" {
|
||||
panic(result.BadRequest("srcUuids cannot be null"))
|
||||
} else {
|
||||
srcUuids = strings.Split(srcUuidsStr, ",")
|
||||
}
|
||||
srcUuids = strings.Split(srcUuidsStr, ",")
|
||||
|
||||
user := this.checkUser(request)
|
||||
|
||||
var destMatter = this.matterDao.CheckWithRootByUuid(destUuid, user)
|
||||
var destMatter = this.matterDao.CheckWithRootByUuid(destUuid, space)
|
||||
if !destMatter.Dir {
|
||||
panic(result.BadRequest("destination is not a directory"))
|
||||
}
|
||||
|
||||
if destMatter.UserUuid != user.Uuid {
|
||||
if destMatter.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
@@ -552,49 +568,41 @@ func (this *MatterController) Move(writer http.ResponseWriter, request *http.Req
|
||||
panic(result.BadRequestI18n(request, i18n.MatterExist, srcMatter.Name))
|
||||
}
|
||||
|
||||
if srcMatter.UserUuid != destMatter.UserUuid {
|
||||
panic("owner not the same")
|
||||
if srcMatter.SpaceUuid != destMatter.SpaceUuid {
|
||||
panic("space not the same")
|
||||
}
|
||||
|
||||
srcMatters = append(srcMatters, srcMatter)
|
||||
}
|
||||
|
||||
this.matterService.AtomicMoveBatch(request, srcMatters, destMatter, user)
|
||||
this.matterService.AtomicMoveBatch(request, srcMatters, destMatter, user, space)
|
||||
|
||||
return this.Success(nil)
|
||||
}
|
||||
|
||||
//mirror local files to EyeblueTank
|
||||
// mirror local files to EyeblueTank
|
||||
func (this *MatterController) Mirror(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
srcPath := request.FormValue("srcPath")
|
||||
destPath := request.FormValue("destPath")
|
||||
overwriteStr := request.FormValue("overwrite")
|
||||
srcPath := util.ExtractRequestString(request, "srcPath")
|
||||
destPath := util.ExtractRequestString(request, "destPath")
|
||||
overwrite := util.ExtractRequestOptionalBool(request, "overwrite", false)
|
||||
|
||||
if srcPath == "" {
|
||||
panic(result.BadRequest("srcPath cannot be null"))
|
||||
}
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckWritableByUuid(request, user, spaceUuid)
|
||||
|
||||
overwrite := false
|
||||
if overwriteStr == TRUE {
|
||||
overwrite = true
|
||||
}
|
||||
|
||||
user := this.userDao.checkUser(request)
|
||||
|
||||
this.matterService.AtomicMirror(request, srcPath, destPath, overwrite, user)
|
||||
this.matterService.AtomicMirror(request, srcPath, destPath, overwrite, user, space)
|
||||
|
||||
return this.Success(nil)
|
||||
|
||||
}
|
||||
|
||||
//download zip.
|
||||
// download zip.
|
||||
func (this *MatterController) Zip(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuids := request.FormValue("uuids")
|
||||
if uuids == "" {
|
||||
panic(result.BadRequest("uuids cannot be null"))
|
||||
}
|
||||
uuids := util.ExtractRequestString(request, "uuids")
|
||||
user := this.checkUser(request)
|
||||
spaceUuid := util.ExtractRequestOptionalString(request, "spaceUuid", user.SpaceUuid)
|
||||
space := this.spaceService.CheckReadableByUuid(request, user, spaceUuid)
|
||||
|
||||
uuidArray := strings.Split(uuids, ",")
|
||||
|
||||
@@ -610,11 +618,10 @@ func (this *MatterController) Zip(writer http.ResponseWriter, request *http.Requ
|
||||
}
|
||||
}
|
||||
|
||||
user := this.checkUser(request)
|
||||
puuid := matters[0].Puuid
|
||||
|
||||
for _, m := range matters {
|
||||
if m.UserUuid != user.Uuid {
|
||||
if m.SpaceUuid != space.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
} else if m.Puuid != puuid {
|
||||
panic(result.BadRequest("puuid not same"))
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"github.com/jinzhu/gorm"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MatterDao struct {
|
||||
@@ -46,7 +47,7 @@ func (this *MatterDao) FindByUuid(uuid string) *Matter {
|
||||
return entity
|
||||
}
|
||||
|
||||
//find by uuid. if not found panic NotFound error
|
||||
// find by uuid. if not found panic NotFound error
|
||||
func (this *MatterDao) CheckByUuid(uuid string) *Matter {
|
||||
entity := this.FindByUuid(uuid)
|
||||
if entity == nil {
|
||||
@@ -56,7 +57,7 @@ func (this *MatterDao) CheckByUuid(uuid string) *Matter {
|
||||
}
|
||||
|
||||
// find by uuid. if uuid=root, then return the Root Matter
|
||||
func (this *MatterDao) CheckWithRootByUuid(uuid string, user *User) *Matter {
|
||||
func (this *MatterDao) CheckWithRootByUuid(uuid string, space *Space) *Matter {
|
||||
|
||||
if uuid == "" {
|
||||
panic(result.BadRequest("uuid cannot be null."))
|
||||
@@ -64,10 +65,10 @@ func (this *MatterDao) CheckWithRootByUuid(uuid string, user *User) *Matter {
|
||||
|
||||
var matter *Matter
|
||||
if uuid == MATTER_ROOT {
|
||||
if user == nil {
|
||||
panic(result.BadRequest("user cannot be null."))
|
||||
if space == nil {
|
||||
panic(result.BadRequest("space cannot be null."))
|
||||
}
|
||||
matter = NewRootMatter(user)
|
||||
matter = NewRootMatter(space)
|
||||
} else {
|
||||
matter = this.CheckByUuid(uuid)
|
||||
}
|
||||
@@ -76,7 +77,7 @@ func (this *MatterDao) CheckWithRootByUuid(uuid string, user *User) *Matter {
|
||||
}
|
||||
|
||||
// find by path. if path=/, then return the Root Matter
|
||||
func (this *MatterDao) CheckWithRootByPath(path string, user *User) *Matter {
|
||||
func (this *MatterDao) CheckWithRootByPath(path string, user *User, space *Space) *Matter {
|
||||
|
||||
var matter *Matter
|
||||
|
||||
@@ -85,7 +86,7 @@ func (this *MatterDao) CheckWithRootByPath(path string, user *User) *Matter {
|
||||
}
|
||||
|
||||
if path == "" || path == "/" {
|
||||
matter = NewRootMatter(user)
|
||||
matter = NewRootMatter(space)
|
||||
} else {
|
||||
matter = this.checkByUserUuidAndPath(user.Uuid, path)
|
||||
}
|
||||
@@ -94,7 +95,7 @@ func (this *MatterDao) CheckWithRootByPath(path string, user *User) *Matter {
|
||||
}
|
||||
|
||||
// find by path. if path=/, then return the Root Matter
|
||||
func (this *MatterDao) FindWithRootByPath(path string, user *User) *Matter {
|
||||
func (this *MatterDao) FindWithRootByPath(path string, user *User, space *Space) *Matter {
|
||||
|
||||
var matter *Matter
|
||||
|
||||
@@ -103,7 +104,7 @@ func (this *MatterDao) FindWithRootByPath(path string, user *User) *Matter {
|
||||
}
|
||||
|
||||
if path == "" || path == "/" {
|
||||
matter = NewRootMatter(user)
|
||||
matter = NewRootMatter(space)
|
||||
} else {
|
||||
matter = this.findByUserUuidAndPath(user.Uuid, path)
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func (this *MatterDao) FindByUserUuidAndPuuidAndDirTrue(userUuid string, puuid s
|
||||
func (this *MatterDao) CheckByUuidAndUserUuid(uuid string, userUuid string) *Matter {
|
||||
|
||||
var matter = &Matter{}
|
||||
db := core.CONTEXT.GetDB().Where(&Matter{Base: Base{Uuid: uuid}, UserUuid: userUuid}).First(matter)
|
||||
db := core.CONTEXT.GetDB().Where(&Matter{Uuid: uuid, UserUuid: userUuid}).First(matter)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return matter
|
||||
@@ -148,7 +149,7 @@ func (this *MatterDao) CheckByUuidAndUserUuid(uuid string, userUuid string) *Mat
|
||||
func (this *MatterDao) CountByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir bool, name string) int {
|
||||
|
||||
var matter Matter
|
||||
var count int
|
||||
var count int64
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
@@ -172,7 +173,69 @@ func (this *MatterDao) CountByUserUuidAndPuuidAndDirAndName(userUuid string, puu
|
||||
Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return count
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func (this *MatterDao) CountBySpaceUuidAndPuuidAndDirAndName(spaceUuid string, puuid string, dir bool, name string) int {
|
||||
|
||||
var matter Matter
|
||||
var count int64
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
if puuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
|
||||
}
|
||||
|
||||
if spaceUuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "space_uuid = ?", Args: []interface{}{spaceUuid}})
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "name = ?", Args: []interface{}{name}})
|
||||
}
|
||||
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{dir}})
|
||||
|
||||
db := core.CONTEXT.GetDB().
|
||||
Model(&matter).
|
||||
Where(wp.Query, wp.Args...).
|
||||
Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindBySpaceUuidAndPuuidAndDirAndName(spaceUuid string, puuid string, dir bool, name string) *Matter {
|
||||
|
||||
var matter = &Matter{}
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
if puuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
|
||||
}
|
||||
|
||||
if spaceUuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "space_uuid = ?", Args: []interface{}{spaceUuid}})
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "name = ?", Args: []interface{}{name}})
|
||||
}
|
||||
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{dir}})
|
||||
|
||||
db := core.CONTEXT.GetDB().Where(wp.Query, wp.Args...).First(matter)
|
||||
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return matter
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir string, name string) *Matter {
|
||||
@@ -212,6 +275,69 @@ func (this *MatterDao) FindByUserUuidAndPuuidAndDirAndName(userUuid string, puui
|
||||
return matter
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindBySpaceNameAndPuuidAndDirAndName(spaceName string, puuid string, dir string, name string) *Matter {
|
||||
|
||||
var matter = &Matter{}
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
if puuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
|
||||
}
|
||||
|
||||
if spaceName != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "space_name = ?", Args: []interface{}{spaceName}})
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "name = ?", Args: []interface{}{name}})
|
||||
}
|
||||
|
||||
if dir == TRUE {
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{true}})
|
||||
} else if dir == FALSE {
|
||||
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{false}})
|
||||
}
|
||||
|
||||
db := core.CONTEXT.GetDB().Where(wp.Query, wp.Args...).First(matter)
|
||||
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return matter
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindByPuuidAndSpaceUuid(puuid string, spaceUuid string, sortArray []builder.OrderPair) []*Matter {
|
||||
var matters []*Matter
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
wp = wp.And(&builder.WherePair{Query: "puuid = ? AND space_uuid = ?", Args: []interface{}{puuid, spaceUuid}})
|
||||
|
||||
if sortArray == nil {
|
||||
|
||||
sortArray = []builder.OrderPair{
|
||||
{
|
||||
Key: "dir",
|
||||
Value: DIRECTION_DESC,
|
||||
},
|
||||
{
|
||||
Key: "create_time",
|
||||
Value: DIRECTION_DESC,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Order(this.GetSortString(sortArray)).Find(&matters)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return matters
|
||||
}
|
||||
|
||||
func (this *MatterDao) FindByPuuidAndUserUuid(puuid string, userUuid string, sortArray []builder.OrderPair) []*Matter {
|
||||
return this.FindByPuuidAndUserUuidAndDeleted(puuid, userUuid, "", sortArray)
|
||||
}
|
||||
@@ -255,11 +381,14 @@ func (this *MatterDao) FindByUuids(uuids []string, sortArray []builder.OrderPair
|
||||
|
||||
return matters
|
||||
}
|
||||
|
||||
// pagination is 0 base.
|
||||
func (this *MatterDao) PlainPage(
|
||||
page int,
|
||||
pageSize int,
|
||||
puuid string,
|
||||
userUuid string,
|
||||
spaceUuid string,
|
||||
name string,
|
||||
dir string,
|
||||
deleted string,
|
||||
@@ -277,6 +406,10 @@ func (this *MatterDao) PlainPage(
|
||||
wp = wp.And(&builder.WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
|
||||
}
|
||||
|
||||
if spaceUuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "space_uuid = ?", Args: []interface{}{spaceUuid}})
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "name LIKE ?", Args: []interface{}{"%" + name + "%"}})
|
||||
}
|
||||
@@ -310,7 +443,7 @@ func (this *MatterDao) PlainPage(
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...)
|
||||
}
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
@@ -318,20 +451,21 @@ func (this *MatterDao) PlainPage(
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&matters)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return count, matters
|
||||
return int(count), matters
|
||||
}
|
||||
func (this *MatterDao) Page(page int, pageSize int, puuid string, userUuid string, name string, dir string, deleted string, extensions []string, sortArray []builder.OrderPair) *Pager {
|
||||
func (this *MatterDao) Page(page int, pageSize int, puuid string, userUuid string, spaceUuid string, name string, dir string, deleted string, extensions []string, sortArray []builder.OrderPair) *Pager {
|
||||
|
||||
count, matters := this.PlainPage(page, pageSize, puuid, userUuid, name, dir, deleted, nil, extensions, sortArray)
|
||||
count, matters := this.PlainPage(page, pageSize, puuid, userUuid, spaceUuid, name, dir, deleted, nil, extensions, sortArray)
|
||||
pager := NewPager(page, pageSize, count, matters)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
//handle matter page by page.
|
||||
// handle matter page by page.
|
||||
func (this *MatterDao) PageHandle(
|
||||
puuid string,
|
||||
userUuid string,
|
||||
spaceUuid string,
|
||||
name string,
|
||||
dir string,
|
||||
deleted string,
|
||||
@@ -349,13 +483,13 @@ func (this *MatterDao) PageHandle(
|
||||
}
|
||||
}
|
||||
|
||||
count, _ := this.PlainPage(0, pageSize, puuid, userUuid, name, dir, deleted, deleteTimeBefore, nil, sortArray)
|
||||
count, _ := this.PlainPage(0, pageSize, puuid, userUuid, spaceUuid, name, dir, deleted, deleteTimeBefore, nil, sortArray)
|
||||
if count > 0 {
|
||||
var totalPages = int(math.Ceil(float64(count) / float64(pageSize)))
|
||||
|
||||
var page int
|
||||
for page = 0; page < totalPages; page++ {
|
||||
_, matters := this.PlainPage(0, pageSize, puuid, userUuid, name, dir, deleted, deleteTimeBefore, nil, sortArray)
|
||||
_, matters := this.PlainPage(0, pageSize, puuid, userUuid, spaceUuid, name, dir, deleted, deleteTimeBefore, nil, sortArray)
|
||||
for _, matter := range matters {
|
||||
fun(matter)
|
||||
}
|
||||
@@ -385,9 +519,9 @@ func (this *MatterDao) Save(matter *Matter) *Matter {
|
||||
return matter
|
||||
}
|
||||
|
||||
//download time add 1
|
||||
// download time add 1
|
||||
func (this *MatterDao) TimesIncrement(matterUuid string) {
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matterUuid).Update(map[string]interface{}{"times": gorm.Expr("times + 1"), "visit_time": time.Now()})
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matterUuid).Updates(map[string]interface{}{"times": gorm.Expr("times + 1"), "visit_time": time.Now()})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
@@ -411,7 +545,27 @@ func (this *MatterDao) SizeByPuuidAndUserUuid(matterUuid string, userUuid string
|
||||
return sumSize
|
||||
}
|
||||
|
||||
//delete a file from db and disk.
|
||||
func (this *MatterDao) SizeByPuuidAndSpaceUuid(matterUuid string, spaceUuid string) int64 {
|
||||
|
||||
var wp = &builder.WherePair{Query: "puuid = ? AND space_uuid = ?", Args: []interface{}{matterUuid, spaceUuid}}
|
||||
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Count(&count)
|
||||
if count == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var sumSize int64
|
||||
db = core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Select("SUM(size)")
|
||||
this.PanicError(db.Error)
|
||||
row := db.Row()
|
||||
err := row.Scan(&sumSize)
|
||||
core.PanicError(err)
|
||||
|
||||
return sumSize
|
||||
}
|
||||
|
||||
// delete a file from db and disk.
|
||||
func (this *MatterDao) Delete(matter *Matter) {
|
||||
|
||||
// recursive if dir
|
||||
@@ -425,9 +579,10 @@ func (this *MatterDao) Delete(matter *Matter) {
|
||||
//delete from db.
|
||||
db := core.CONTEXT.GetDB().Delete(&matter)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
//delete dir from disk.
|
||||
util.DeleteEmptyDir(matter.AbsolutePath())
|
||||
if util.PathExists(matter.AbsolutePath()) {
|
||||
//delete dir from disk.
|
||||
util.DeleteEmptyDir(matter.AbsolutePath())
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -450,20 +605,20 @@ func (this *MatterDao) Delete(matter *Matter) {
|
||||
}
|
||||
}
|
||||
|
||||
//soft delete a file or dir
|
||||
// soft delete a file or dir
|
||||
func (this *MatterDao) SoftDelete(matter *Matter) {
|
||||
|
||||
//soft delete from db.
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matter.Uuid).Update(map[string]interface{}{"deleted": true, "delete_time": time.Now()})
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matter.Uuid).Updates(map[string]interface{}{"deleted": true, "delete_time": time.Now()})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
//recovery a file
|
||||
// recovery a file
|
||||
func (this *MatterDao) Recovery(matter *Matter) {
|
||||
|
||||
//recovery from db.
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matter.Uuid).Update(map[string]interface{}{"deleted": false, "delete_time": time.Now()})
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matter.Uuid).Updates(map[string]interface{}{"deleted": false, "delete_time": time.Now()})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
@@ -484,7 +639,7 @@ func (this *MatterDao) CountBetweenTime(startTime time.Time, endTime time.Time)
|
||||
|
||||
func (this *MatterDao) SizeBetweenTime(startTime time.Time, endTime time.Time) int64 {
|
||||
|
||||
var wp = &builder.WherePair{Query: "create_time >= ? AND create_time <= ?", Args: []interface{}{startTime, endTime}}
|
||||
var wp = &builder.WherePair{Query: "dir = 0 AND create_time >= ? AND create_time <= ?", Args: []interface{}{startTime, endTime}}
|
||||
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Count(&count)
|
||||
@@ -501,7 +656,7 @@ func (this *MatterDao) SizeBetweenTime(startTime time.Time, endTime time.Time) i
|
||||
return size
|
||||
}
|
||||
|
||||
//find by userUuid and path. if not found, return nil
|
||||
// find by userUuid and path. if not found, return nil
|
||||
func (this *MatterDao) findByUserUuidAndPath(userUuid string, path string) *Matter {
|
||||
|
||||
var wp = &builder.WherePair{Query: "user_uuid = ? AND path = ?", Args: []interface{}{userUuid, path}}
|
||||
@@ -520,7 +675,7 @@ func (this *MatterDao) findByUserUuidAndPath(userUuid string, path string) *Matt
|
||||
return matter
|
||||
}
|
||||
|
||||
//find by userUuid and path. if not found, panic
|
||||
// find by userUuid and path. if not found, panic
|
||||
func (this *MatterDao) checkByUserUuidAndPath(userUuid string, path string) *Matter {
|
||||
|
||||
if path == "" {
|
||||
@@ -555,6 +710,11 @@ func (this *MatterDao) SumSizeByUserUuidAndPath(userUuid string, path string) in
|
||||
|
||||
}
|
||||
|
||||
func (this *MatterDao) UpdateSize(matterUuid string, size int64) {
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where("uuid = ?", matterUuid).Update("size", size)
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
func (this *MatterDao) CountByUserUuidAndPath(userUuid string, path string) int64 {
|
||||
|
||||
var wp = &builder.WherePair{Query: "user_uuid = ? AND path like ?", Args: []interface{}{userUuid, path + "%"}}
|
||||
@@ -567,7 +727,19 @@ func (this *MatterDao) CountByUserUuidAndPath(userUuid string, path string) int6
|
||||
|
||||
}
|
||||
|
||||
//统计总共有多少条。
|
||||
func (this *MatterDao) CountByUserUuid(userUuid string) int64 {
|
||||
|
||||
var wp = &builder.WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}}
|
||||
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Count(&count)
|
||||
core.PanicError(db.Error)
|
||||
|
||||
return count
|
||||
|
||||
}
|
||||
|
||||
// 统计总共有多少条。
|
||||
func (this *MatterDao) Count() int64 {
|
||||
|
||||
var count int64
|
||||
@@ -578,7 +750,7 @@ func (this *MatterDao) Count() int64 {
|
||||
|
||||
}
|
||||
|
||||
//System cleanup.
|
||||
// System cleanup.
|
||||
func (this *MatterDao) Cleanup() {
|
||||
this.logger.Info("[MatterDao] clean up. Delete all Matter record in db and on disk.")
|
||||
db := core.CONTEXT.GetDB().Where("uuid is not null").Delete(Matter{})
|
||||
|
||||
@@ -30,10 +30,14 @@ const (
|
||||
* file is too common. so we use matter as file.
|
||||
*/
|
||||
type Matter struct {
|
||||
Base
|
||||
Puuid string `json:"puuid" gorm:"type:char(36);index:idx_puuid"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36);index:idx_uu"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Puuid string `json:"puuid" gorm:"type:char(36);index:idx_matter_puuid"` //index should unique globally for sqlite.
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36);index:idx_matter_uu"`
|
||||
//TODO: check field usage.
|
||||
SpaceName string `json:"space_name" gorm:"type:varchar(45) not null"`
|
||||
Dir bool `json:"dir" gorm:"type:tinyint(1) not null;default:0"`
|
||||
Name string `json:"name" gorm:"type:varchar(255) not null"`
|
||||
Md5 string `json:"md5" gorm:"type:varchar(45)"`
|
||||
@@ -41,44 +45,42 @@ type Matter struct {
|
||||
Privacy bool `json:"privacy" gorm:"type:tinyint(1) not null;default:0"`
|
||||
Path string `json:"path" gorm:"type:varchar(1024)"`
|
||||
Times int64 `json:"times" gorm:"type:bigint(20) not null;default:0"`
|
||||
Parent *Matter `json:"parent" gorm:"-"`
|
||||
Children []*Matter `json:"-" gorm:"-"`
|
||||
Prop string `json:"prop" gorm:"type:varchar(1024) not null;default:'{}'"`
|
||||
VisitTime time.Time `json:"visitTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Deleted bool `json:"deleted" gorm:"type:tinyint(1) not null;index:idx_del;default:0"`
|
||||
DeleteTime time.Time `json:"deleteTime" gorm:"type:timestamp not null;index:idx_delt;default:'2018-01-01 00:00:00'"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (Matter) TableName() string {
|
||||
return core.TABLE_PREFIX + "matter"
|
||||
Deleted bool `json:"deleted" gorm:"type:tinyint(1) not null;index:idx_matter_del;default:0"`
|
||||
DeleteTime time.Time `json:"deleteTime" gorm:"type:timestamp not null;index:idx_matter_delt;default:'2018-01-01 00:00:00'"`
|
||||
SpaceUuid string `json:"spaceUuid" gorm:"type:char(36) not null;index:idx_matter_space_uuid"`
|
||||
User *User `json:"user" gorm:"-"`
|
||||
Parent *Matter `json:"parent" gorm:"-"`
|
||||
Children []*Matter `json:"-" gorm:"-"`
|
||||
}
|
||||
|
||||
// get matter's absolute path. the Path property is relative path in db.
|
||||
func (this *Matter) AbsolutePath() string {
|
||||
return GetUserMatterRootDir(this.Username) + this.Path
|
||||
return GetSpaceMatterRootDir(this.SpaceName) + this.Path
|
||||
}
|
||||
|
||||
func (this *Matter) MimeType() string {
|
||||
return util.GetMimeType(util.GetExtension(this.Name))
|
||||
}
|
||||
|
||||
//Create a root matter. It's convenient for copy and move
|
||||
func NewRootMatter(user *User) *Matter {
|
||||
// Create a root matter. It's convenient for copy and move
|
||||
func NewRootMatter(space *Space) *Matter {
|
||||
matter := &Matter{}
|
||||
matter.Uuid = MATTER_ROOT
|
||||
matter.UserUuid = user.Uuid
|
||||
matter.Username = user.Username
|
||||
matter.UserUuid = space.UserUuid
|
||||
matter.SpaceUuid = space.Uuid
|
||||
matter.SpaceName = space.Name
|
||||
matter.Dir = true
|
||||
matter.Path = ""
|
||||
matter.CreateTime = user.CreateTime
|
||||
matter.UpdateTime = user.UpdateTime
|
||||
matter.VisitTime = user.UpdateTime
|
||||
matter.CreateTime = space.CreateTime
|
||||
matter.UpdateTime = space.UpdateTime
|
||||
matter.VisitTime = space.UpdateTime
|
||||
|
||||
return matter
|
||||
}
|
||||
|
||||
//get user's space absolute path
|
||||
// get user's space absolute path
|
||||
func GetUserSpaceRootDir(username string) (rootDirPath string) {
|
||||
|
||||
rootDirPath = fmt.Sprintf("%s/%s", core.CONFIG.MatterPath(), username)
|
||||
@@ -86,31 +88,31 @@ func GetUserSpaceRootDir(username string) (rootDirPath string) {
|
||||
return rootDirPath
|
||||
}
|
||||
|
||||
//get user's root absolute path
|
||||
func GetUserMatterRootDir(username string) (rootDirPath string) {
|
||||
// get user's root absolute path
|
||||
func GetSpaceMatterRootDir(spaceName string) (rootDirPath string) {
|
||||
|
||||
rootDirPath = fmt.Sprintf("%s/%s/%s", core.CONFIG.MatterPath(), username, MATTER_ROOT)
|
||||
rootDirPath = fmt.Sprintf("%s/%s/%s", core.CONFIG.MatterPath(), spaceName, MATTER_ROOT)
|
||||
|
||||
return rootDirPath
|
||||
}
|
||||
|
||||
//get user's cache absolute path
|
||||
func GetUserCacheRootDir(username string) (rootDirPath string) {
|
||||
// get user's cache absolute path
|
||||
func GetSpaceCacheRootDir(spaceName string) (rootDirPath string) {
|
||||
|
||||
rootDirPath = fmt.Sprintf("%s/%s/%s", core.CONFIG.MatterPath(), username, MATTER_CACHE)
|
||||
rootDirPath = fmt.Sprintf("%s/%s/%s", core.CONFIG.MatterPath(), spaceName, MATTER_CACHE)
|
||||
|
||||
return rootDirPath
|
||||
}
|
||||
|
||||
//get user's zip absolute path
|
||||
func GetUserZipRootDir(username string) (rootDirPath string) {
|
||||
// get user's zip absolute path
|
||||
func GetSpaceZipRootDir(spaceName string) (rootDirPath string) {
|
||||
|
||||
rootDirPath = fmt.Sprintf("%s/%s/%s", core.CONFIG.MatterPath(), username, MATTER_ZIP)
|
||||
rootDirPath = fmt.Sprintf("%s/%s/%s", core.CONFIG.MatterPath(), spaceName, MATTER_ZIP)
|
||||
|
||||
return rootDirPath
|
||||
}
|
||||
|
||||
//check matter's name. If error, panic.
|
||||
// check matter's name. If error, panic.
|
||||
func CheckMatterName(request *http.Request, name string) string {
|
||||
|
||||
if name == "" {
|
||||
@@ -129,7 +131,7 @@ func CheckMatterName(request *http.Request, name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
//fetch the props
|
||||
// fetch the props
|
||||
func (this *Matter) FetchPropMap() map[string]string {
|
||||
|
||||
m := make(map[string]string)
|
||||
@@ -146,13 +148,12 @@ func (this *Matter) FetchPropMap() map[string]string {
|
||||
return m
|
||||
}
|
||||
|
||||
//fetch the props
|
||||
// fetch the props
|
||||
func (this *Matter) SetPropMap(propMap map[string]string) {
|
||||
|
||||
b, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(propMap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
this.Prop = string(b)
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func (this *PreferenceController) RegisterRoutes() map[string]func(writer http.R
|
||||
return routeMap
|
||||
}
|
||||
|
||||
//ping the application. Return current version.
|
||||
// ping the application. Return current version.
|
||||
func (this *PreferenceController) Ping(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
return this.Success(core.VERSION)
|
||||
@@ -76,7 +76,7 @@ func (this *PreferenceController) Fetch(writer http.ResponseWriter, request *htt
|
||||
return this.Success(preference)
|
||||
}
|
||||
|
||||
//edit basic info.
|
||||
// edit basic info.
|
||||
func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
name := request.FormValue("name")
|
||||
@@ -89,6 +89,7 @@ func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http
|
||||
downloadDirMaxNumStr := request.FormValue("downloadDirMaxNum")
|
||||
defaultTotalSizeLimitStr := request.FormValue("defaultTotalSizeLimit")
|
||||
allowRegisterStr := request.FormValue("allowRegister")
|
||||
allowChunkUploadStr := request.FormValue("allowChunkUpload")
|
||||
deletedKeepDaysStr := request.FormValue("deletedKeepDays")
|
||||
|
||||
if name == "" {
|
||||
@@ -140,6 +141,11 @@ func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http
|
||||
allowRegister = true
|
||||
}
|
||||
|
||||
var allowChunkUpload = false
|
||||
if allowChunkUploadStr == TRUE {
|
||||
allowChunkUpload = true
|
||||
}
|
||||
|
||||
preference := this.preferenceDao.Fetch()
|
||||
oldDeletedKeepDays := preference.DeletedKeepDays
|
||||
preference.Name = name
|
||||
@@ -151,6 +157,7 @@ func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http
|
||||
preference.DownloadDirMaxNum = downloadDirMaxNum
|
||||
preference.DefaultTotalSizeLimit = defaultTotalSizeLimit
|
||||
preference.AllowRegister = allowRegister
|
||||
preference.AllowChunkUpload = allowChunkUpload
|
||||
preference.DeletedKeepDays = deletedKeepDays
|
||||
|
||||
preference = this.preferenceService.Save(preference)
|
||||
@@ -163,7 +170,7 @@ func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http
|
||||
return this.Success(preference)
|
||||
}
|
||||
|
||||
//edit preview config.
|
||||
// edit preview config.
|
||||
func (this *PreferenceController) EditPreviewConfig(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
previewConfig := request.FormValue("previewConfig")
|
||||
@@ -200,7 +207,7 @@ func (this *PreferenceController) EditScanConfig(writer http.ResponseWriter, req
|
||||
|
||||
//validate scope.
|
||||
if scanConfig.Scope == SCAN_SCOPE_CUSTOM {
|
||||
if len(scanConfig.Usernames) == 0 {
|
||||
if len(scanConfig.SpaceNames) == 0 {
|
||||
panic(result.BadRequest("scope cannot be null"))
|
||||
}
|
||||
} else if scanConfig.Scope == SCAN_SCOPE_ALL {
|
||||
@@ -219,14 +226,14 @@ func (this *PreferenceController) EditScanConfig(writer http.ResponseWriter, req
|
||||
return this.Success(preference)
|
||||
}
|
||||
|
||||
//scan immediately according the current config.
|
||||
// scan immediately according the current config.
|
||||
func (this *PreferenceController) ScanOnce(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
this.taskService.doScanTask()
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
//cleanup system data.
|
||||
// cleanup system data.
|
||||
func (this *PreferenceController) SystemCleanup(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
user := this.checkUser(request)
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Preference struct {
|
||||
Base
|
||||
Name string `json:"name" gorm:"type:varchar(45)"`
|
||||
LogoUrl string `json:"logoUrl" gorm:"type:varchar(255)"`
|
||||
FaviconUrl string `json:"faviconUrl" gorm:"type:varchar(255)"`
|
||||
Copyright string `json:"copyright" gorm:"type:varchar(1024)"`
|
||||
Record string `json:"record" gorm:"type:varchar(1024)"`
|
||||
DownloadDirMaxSize int64 `json:"downloadDirMaxSize" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DownloadDirMaxNum int64 `json:"downloadDirMaxNum" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DefaultTotalSizeLimit int64 `json:"defaultTotalSizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
AllowRegister bool `json:"allowRegister" gorm:"type:tinyint(1) not null;default:0"`
|
||||
PreviewConfig string `json:"previewConfig" gorm:"type:text"`
|
||||
ScanConfig string `json:"scanConfig" gorm:"type:text"`
|
||||
DeletedKeepDays int64 `json:"deletedKeepDays" gorm:"type:bigint(20) not null;default:7"`
|
||||
Version string `json:"version" gorm:"-"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *Preference) TableName() string {
|
||||
return core.TABLE_PREFIX + "preference"
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Name string `json:"name" gorm:"type:varchar(45)"`
|
||||
LogoUrl string `json:"logoUrl" gorm:"type:varchar(255)"`
|
||||
FaviconUrl string `json:"faviconUrl" gorm:"type:varchar(255)"`
|
||||
Copyright string `json:"copyright" gorm:"type:varchar(1024)"`
|
||||
Record string `json:"record" gorm:"type:varchar(1024)"`
|
||||
DownloadDirMaxSize int64 `json:"downloadDirMaxSize" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DownloadDirMaxNum int64 `json:"downloadDirMaxNum" gorm:"type:bigint(20) not null;default:-1"`
|
||||
DefaultTotalSizeLimit int64 `json:"defaultTotalSizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
AllowRegister bool `json:"allowRegister" gorm:"type:tinyint(1) not null;default:0"`
|
||||
AllowChunkUpload bool `json:"allowChunkUpload" gorm:"type:tinyint(1) not null;default:0"`
|
||||
PreviewConfig string `json:"previewConfig" gorm:"type:text"`
|
||||
ScanConfig string `json:"scanConfig" gorm:"type:text"`
|
||||
DeletedKeepDays int64 `json:"deletedKeepDays" gorm:"type:bigint(20) not null;default:7"`
|
||||
Version string `json:"version" gorm:"-"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -34,19 +33,19 @@ const (
|
||||
SCAN_SCOPE_CUSTOM = "CUSTOM"
|
||||
)
|
||||
|
||||
//scan config struct.
|
||||
// scan config struct.
|
||||
type ScanConfig struct {
|
||||
//whether enable the scan task.
|
||||
Enable bool `json:"enable"`
|
||||
//when to process the task. five fields. @every 1s
|
||||
Cron string `json:"cron"`
|
||||
//username
|
||||
Usernames []string `json:"usernames"`
|
||||
//spaceNames
|
||||
SpaceNames []string `json:"spaceNames"`
|
||||
//scan scope. see SCAN_SCOPE
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
//fetch the scan config
|
||||
// fetch the scan config
|
||||
func (this *Preference) FetchScanConfig() *ScanConfig {
|
||||
|
||||
json := this.ScanConfig
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
Base
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
|
||||
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
}
|
||||
|
||||
// set User's table name to be `profiles`
|
||||
func (this *Session) TableName() string {
|
||||
return core.TABLE_PREFIX + "session"
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type ShareController struct {
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
shareService *ShareService
|
||||
alienService *AlienService
|
||||
}
|
||||
|
||||
func (this *ShareController) Init() {
|
||||
@@ -49,6 +50,11 @@ func (this *ShareController) Init() {
|
||||
this.shareService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.alienService)
|
||||
if b, ok := b.(*AlienService); ok {
|
||||
this.alienService = b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *ShareController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
|
||||
@@ -63,43 +69,28 @@ func (this *ShareController) RegisterRoutes() map[string]func(writer http.Respon
|
||||
routeMap["/api/share/browse"] = this.Wrap(this.Browse, USER_ROLE_GUEST)
|
||||
routeMap["/api/share/zip"] = this.Wrap(this.Zip, USER_ROLE_GUEST)
|
||||
|
||||
routeMap["/api/share/matter/page"] = this.Wrap(this.MatterPage, USER_ROLE_GUEST)
|
||||
routeMap["/api/share/matter/preview"] = this.WrapPure(this.MatterPreview, USER_ROLE_GUEST)
|
||||
routeMap["/api/share/matter/download"] = this.WrapPure(this.MatterDownload, USER_ROLE_GUEST)
|
||||
|
||||
return routeMap
|
||||
}
|
||||
|
||||
func (this *ShareController) Create(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
matterUuids := request.FormValue("matterUuids")
|
||||
expireInfinityStr := request.FormValue("expireInfinity")
|
||||
expireTimeStr := request.FormValue("expireTime")
|
||||
|
||||
if matterUuids == "" {
|
||||
panic(result.BadRequest("matterUuids cannot be null"))
|
||||
}
|
||||
|
||||
var expireTime time.Time
|
||||
expireInfinity := false
|
||||
if expireInfinityStr == TRUE {
|
||||
expireInfinity = true
|
||||
expireTime = time.Now()
|
||||
} else {
|
||||
|
||||
if expireTimeStr == "" {
|
||||
panic(result.BadRequest("time format error"))
|
||||
} else {
|
||||
expireTime = util.ConvertDateTimeStringToTime(expireTimeStr)
|
||||
}
|
||||
uuidArray := util.ExtractRequestArray(request, "matterUuids")
|
||||
expireInfinity := util.ExtractRequestBool(request, "expireInfinity")
|
||||
spaceUuid := util.ExtractRequestString(request, "spaceUuid")
|
||||
|
||||
var expireTime = time.Now()
|
||||
if !expireInfinity {
|
||||
expireTime = util.ExtractRequestTime(request, "expireTime")
|
||||
if expireTime.Before(time.Now()) {
|
||||
panic(result.BadRequest("expire time cannot before now"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uuidArray := strings.Split(matterUuids, ",")
|
||||
|
||||
if len(uuidArray) == 0 {
|
||||
panic(result.BadRequest("share at least one file"))
|
||||
} else if len(uuidArray) > SHARE_MAX_NUM {
|
||||
if len(uuidArray) > SHARE_MAX_NUM {
|
||||
panic(result.BadRequestI18n(request, i18n.ShareNumExceedLimit, len(uuidArray), SHARE_MAX_NUM))
|
||||
}
|
||||
|
||||
@@ -148,6 +139,7 @@ func (this *ShareController) Create(writer http.ResponseWriter, request *http.Re
|
||||
Code: util.RandomString4(),
|
||||
ExpireInfinity: expireInfinity,
|
||||
ExpireTime: expireTime,
|
||||
SpaceUuid: spaceUuid,
|
||||
}
|
||||
this.shareDao.Create(share)
|
||||
|
||||
@@ -192,14 +184,14 @@ func (this *ShareController) DeleteBatch(writer http.ResponseWriter, request *ht
|
||||
|
||||
for _, uuid := range uuidArray {
|
||||
|
||||
imageCache := this.shareDao.FindByUuid(uuid)
|
||||
share := this.shareDao.FindByUuid(uuid)
|
||||
|
||||
user := this.checkUser(request)
|
||||
if imageCache.UserUuid != user.Uuid {
|
||||
if share.UserUuid != user.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
this.shareDao.Delete(imageCache)
|
||||
this.shareDao.Delete(share)
|
||||
}
|
||||
|
||||
return this.Success("OK")
|
||||
@@ -266,6 +258,7 @@ func (this *ShareController) CheckShare(writer http.ResponseWriter, request *htt
|
||||
return this.shareService.CheckShare(request, shareUuid, code, user)
|
||||
}
|
||||
|
||||
// get the basic info of this share.
|
||||
func (this *ShareController) Browse(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
shareUuid := request.FormValue("shareUuid")
|
||||
@@ -278,6 +271,10 @@ func (this *ShareController) Browse(writer http.ResponseWriter, request *http.Re
|
||||
user := this.findUser(request)
|
||||
share := this.shareService.CheckShare(request, shareUuid, code, user)
|
||||
bridges := this.bridgeDao.FindByShareUuid(share.Uuid)
|
||||
shareOwner := this.userDao.FindByUuid(share.UserUuid)
|
||||
if shareOwner.Status == USER_STATUS_DISABLED {
|
||||
panic(result.BadRequestI18n(request, i18n.UserDisabled))
|
||||
}
|
||||
|
||||
if puuid == MATTER_ROOT {
|
||||
|
||||
@@ -371,3 +368,89 @@ func (this *ShareController) Zip(writer http.ResponseWriter, request *http.Reque
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// matter list under a share.
|
||||
func (this *ShareController) MatterPage(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
page := util.ExtractRequestOptionalInt(request, "page", 0)
|
||||
pageSize := util.ExtractRequestOptionalInt(request, "pageSize", 200)
|
||||
orderCreateTime := util.ExtractRequestOptionalString(request, "orderCreateTime", "")
|
||||
orderUpdateTime := util.ExtractRequestOptionalString(request, "orderUpdateTime", "")
|
||||
orderDeleteTime := util.ExtractRequestOptionalString(request, "orderDeleteTime", "")
|
||||
orderSort := util.ExtractRequestOptionalString(request, "orderSort", "")
|
||||
orderTimes := util.ExtractRequestOptionalString(request, "orderTimes", "")
|
||||
orderDir := util.ExtractRequestOptionalString(request, "orderDir", "")
|
||||
orderSize := util.ExtractRequestOptionalString(request, "orderSize", "")
|
||||
orderName := util.ExtractRequestOptionalString(request, "orderName", "")
|
||||
|
||||
puuid := util.ExtractRequestString(request, "puuid")
|
||||
name := util.ExtractRequestOptionalString(request, "name", "")
|
||||
dir := util.ExtractRequestOptionalString(request, "dir", "")
|
||||
deleted := util.ExtractRequestOptionalString(request, "deleted", "")
|
||||
extensionsStr := util.ExtractRequestOptionalString(request, "extensions", "")
|
||||
|
||||
//auth by shareUuid.
|
||||
shareUuid := util.ExtractRequestString(request, "shareUuid")
|
||||
shareCode := util.ExtractRequestString(request, "shareCode")
|
||||
shareRootUuid := util.ExtractRequestString(request, "shareRootUuid")
|
||||
|
||||
//validate the puuid.
|
||||
dirMatter := this.matterDao.CheckByUuid(puuid)
|
||||
if !dirMatter.Dir {
|
||||
panic(result.BadRequest("puuid is not a directory"))
|
||||
}
|
||||
|
||||
//validate the shareUuid,shareCode,shareRootUuid.
|
||||
user := this.findUser(request)
|
||||
share := this.shareService.ValidateMatter(request, shareUuid, shareCode, user, shareRootUuid, dirMatter)
|
||||
puuid = dirMatter.Uuid
|
||||
|
||||
var extensions []string
|
||||
if extensionsStr != "" {
|
||||
extensions = strings.Split(extensionsStr, ",")
|
||||
}
|
||||
|
||||
pager := this.matterService.Page(
|
||||
request,
|
||||
page,
|
||||
pageSize,
|
||||
orderCreateTime,
|
||||
orderUpdateTime,
|
||||
orderDeleteTime,
|
||||
orderSort,
|
||||
orderTimes,
|
||||
orderDir,
|
||||
orderSize,
|
||||
orderName,
|
||||
puuid,
|
||||
name,
|
||||
dir,
|
||||
deleted,
|
||||
extensions,
|
||||
share.SpaceUuid,
|
||||
)
|
||||
|
||||
return this.Success(pager)
|
||||
}
|
||||
|
||||
func (this *ShareController) MatterPreviewOrDownload(writer http.ResponseWriter, request *http.Request, withContentDisposition bool) {
|
||||
//auth by shareUuid.
|
||||
matterUuid := util.ExtractRequestString(request, "matterUuid")
|
||||
shareUuid := util.ExtractRequestString(request, "shareUuid")
|
||||
shareCode := util.ExtractRequestString(request, "shareCode")
|
||||
shareRootUuid := util.ExtractRequestString(request, "shareRootUuid")
|
||||
|
||||
matter := this.matterDao.CheckByUuid(matterUuid)
|
||||
operator := this.findUser(request)
|
||||
|
||||
this.shareService.ValidateMatter(request, shareUuid, shareCode, operator, shareRootUuid, matter)
|
||||
this.alienService.PreviewOrDownload(writer, request, matter, withContentDisposition)
|
||||
}
|
||||
|
||||
func (this *ShareController) MatterPreview(writer http.ResponseWriter, request *http.Request) {
|
||||
this.MatterPreviewOrDownload(writer, request, false)
|
||||
}
|
||||
|
||||
func (this *ShareController) MatterDownload(writer http.ResponseWriter, request *http.Request) {
|
||||
this.MatterPreviewOrDownload(writer, request, true)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
@@ -56,7 +56,7 @@ func (this *ShareDao) PlainPage(page int, pageSize int, userUuid string, sortArr
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Share{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
count := 0
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
@@ -64,7 +64,7 @@ func (this *ShareDao) PlainPage(page int, pageSize int, userUuid string, sortArr
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&shares)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return count, shares
|
||||
return int(count), shares
|
||||
}
|
||||
|
||||
func (this *ShareDao) Create(share *Share) *Share {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -22,7 +21,10 @@ const (
|
||||
* share record
|
||||
*/
|
||||
type Share struct {
|
||||
Base
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
Name string `json:"name" gorm:"type:varchar(255)"`
|
||||
ShareType string `json:"shareType" gorm:"type:varchar(45)"`
|
||||
Username string `json:"username" gorm:"type:varchar(45)"`
|
||||
@@ -31,11 +33,7 @@ type Share struct {
|
||||
Code string `json:"code" gorm:"type:varchar(45) not null"`
|
||||
ExpireInfinity bool `json:"expireInfinity" gorm:"type:tinyint(1) not null;default:0"`
|
||||
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
SpaceUuid string `json:"spaceUuid" gorm:"type:char(36) not null;index:idx_share_space_uuid"`
|
||||
DirMatter *Matter `json:"dirMatter" gorm:"-"`
|
||||
Matters []*Matter `json:"matters" gorm:"-"`
|
||||
}
|
||||
|
||||
// set File's table name to be `profiles`
|
||||
func (this *Share) TableName() string {
|
||||
return core.TABLE_PREFIX + "share"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//@Service
|
||||
// @Service
|
||||
type ShareService struct {
|
||||
BaseBean
|
||||
shareDao *ShareDao
|
||||
@@ -74,24 +74,24 @@ func (this *ShareService) CheckShare(request *http.Request, shareUuid string, co
|
||||
return share
|
||||
}
|
||||
|
||||
//check whether a user can access a matter. shareRootUuid is matter's parent(or parent's parent and so on)
|
||||
func (this *ShareService) ValidateMatter(request *http.Request, shareUuid string, code string, user *User, shareRootUuid string, matter *Matter) {
|
||||
// check whether a user can access a matter. shareRootUuid is matter's parent(or parent's parent and so on)
|
||||
func (this *ShareService) ValidateMatter(request *http.Request, shareUuid string, code string, user *User, shareRootUuid string, matter *Matter) *Share {
|
||||
|
||||
if matter == nil {
|
||||
panic(result.Unauthorized("matter cannot be nil"))
|
||||
}
|
||||
|
||||
//if self's matter, ok.
|
||||
if user != nil && matter.UserUuid == user.Uuid {
|
||||
return
|
||||
}
|
||||
|
||||
if shareUuid == "" || code == "" || shareRootUuid == "" {
|
||||
panic(result.Unauthorized("shareUuid,code,shareRootUuid cannot be null"))
|
||||
}
|
||||
|
||||
share := this.CheckShare(request, shareUuid, code, user)
|
||||
|
||||
shareOwner := this.userDao.FindByUuid(share.UserUuid)
|
||||
if shareOwner.Status == USER_STATUS_DISABLED {
|
||||
panic(result.BadRequestI18n(request, i18n.UserDisabled))
|
||||
}
|
||||
|
||||
//if shareRootUuid is root. Bridge must has record.
|
||||
if shareRootUuid == MATTER_ROOT {
|
||||
|
||||
@@ -109,9 +109,11 @@ func (this *ShareService) ValidateMatter(request *http.Request, shareUuid string
|
||||
}
|
||||
}
|
||||
|
||||
return share
|
||||
|
||||
}
|
||||
|
||||
//delete user's shares and corresponding bridges.
|
||||
// delete user's shares and corresponding bridges.
|
||||
func (this *ShareService) DeleteSharesByUser(request *http.Request, currentUser *User) {
|
||||
|
||||
//delete share and bridges.
|
||||
@@ -135,3 +137,8 @@ func (this *ShareService) DeleteSharesByUser(request *http.Request, currentUser
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// edit space's info.
|
||||
func (this *ShareService) Edit(spaceUuid string, sizeLimit int64, totalSizeLimit int64) {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SpaceController struct {
|
||||
BaseController
|
||||
spaceDao *SpaceDao
|
||||
spaceMemberDao *SpaceMemberDao
|
||||
spaceMemberService *SpaceMemberService
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
spaceService *SpaceService
|
||||
userService *UserService
|
||||
}
|
||||
|
||||
func (this *SpaceController) Init() {
|
||||
this.BaseController.Init()
|
||||
|
||||
b := core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceMemberDao)
|
||||
if b, ok := b.(*SpaceMemberDao); ok {
|
||||
this.spaceMemberDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceMemberService)
|
||||
if b, ok := b.(*SpaceMemberService); ok {
|
||||
this.spaceMemberService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterDao)
|
||||
if b, ok := b.(*MatterDao); ok {
|
||||
this.matterDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterService)
|
||||
if b, ok := b.(*MatterService); ok {
|
||||
this.matterService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceService)
|
||||
if b, ok := b.(*SpaceService); ok {
|
||||
this.spaceService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.userService)
|
||||
if b, ok := b.(*UserService); ok {
|
||||
this.userService = b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
|
||||
|
||||
routeMap["/api/space/create"] = this.Wrap(this.Create, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/space/edit"] = this.Wrap(this.Edit, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/space/delete"] = this.Wrap(this.Delete, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/space/detail"] = this.Wrap(this.Detail, USER_ROLE_USER)
|
||||
routeMap["/api/space/page"] = this.Wrap(this.Page, USER_ROLE_USER)
|
||||
return routeMap
|
||||
}
|
||||
|
||||
func (this *SpaceController) Create(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
//space's name
|
||||
name := util.ExtractRequestString(request, "name")
|
||||
sizeLimit := util.ExtractRequestInt64(request, "sizeLimit")
|
||||
totalSizeLimit := util.ExtractRequestInt64(request, "totalSizeLimit")
|
||||
|
||||
//create related space.
|
||||
space := this.spaceService.CreateSpace(request, name, nil, sizeLimit, totalSizeLimit, SPACE_TYPE_SHARED)
|
||||
|
||||
return this.Success(space)
|
||||
}
|
||||
|
||||
func (this *SpaceController) Edit(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
//space's uuid
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
sizeLimit := util.ExtractRequestInt64(request, "sizeLimit")
|
||||
totalSizeLimit := util.ExtractRequestInt64(request, "totalSizeLimit")
|
||||
|
||||
user := this.checkUser(request)
|
||||
space := this.spaceService.Edit(request, user, uuid, sizeLimit, totalSizeLimit)
|
||||
|
||||
return this.Success(space)
|
||||
}
|
||||
|
||||
func (this *SpaceController) Delete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
//space's name
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
space := this.spaceDao.CheckByUuid(uuid)
|
||||
|
||||
//when space has members, cannot delete.
|
||||
memberCount := this.spaceMemberDao.CountBySpaceUuid(uuid)
|
||||
if memberCount > 0 {
|
||||
panic(result.BadRequest("space has members, cannot be deleted."))
|
||||
}
|
||||
|
||||
//TODO: when space has files, cannot delete.
|
||||
|
||||
//delete the space.
|
||||
this.spaceDao.Delete(space)
|
||||
|
||||
return this.Success(nil)
|
||||
}
|
||||
|
||||
func (this *SpaceController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
space := this.spaceDao.CheckByUuid(uuid)
|
||||
canRead := this.spaceMemberService.canRead(user, space.Uuid)
|
||||
if !canRead {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
return this.Success(space)
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
page := util.ExtractRequestOptionalInt(request, "page", 0)
|
||||
pageSize := util.ExtractRequestOptionalInt(request, "pageSize", 20)
|
||||
orderCreateTime := util.ExtractRequestOptionalString(request, "orderCreateTime", "")
|
||||
spaceType := util.ExtractRequestOptionalString(request, "type", "")
|
||||
name := util.ExtractRequestOptionalString(request, "name", "")
|
||||
|
||||
user := this.checkUser(request)
|
||||
|
||||
sortArray := []builder.OrderPair{
|
||||
{
|
||||
Key: "create_time",
|
||||
Value: orderCreateTime,
|
||||
},
|
||||
}
|
||||
|
||||
var pager *Pager
|
||||
if user.Role == USER_ROLE_USER {
|
||||
if spaceType != SPACE_TYPE_SHARED {
|
||||
panic(result.BadRequest("user can only query shared space type."))
|
||||
}
|
||||
pager = this.spaceDao.SelfPage(page, pageSize, user.Uuid, spaceType, sortArray)
|
||||
} else if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
pager = this.spaceDao.Page(page, pageSize, spaceType, name, sortArray)
|
||||
}
|
||||
|
||||
return this.Success(pager)
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"gorm.io/gorm"
|
||||
"math"
|
||||
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SpaceDao struct {
|
||||
BaseDao
|
||||
}
|
||||
|
||||
// find by uuid. if not found return nil.
|
||||
func (this *SpaceDao) FindByUuid(uuid string) *Space {
|
||||
var entity = &Space{}
|
||||
db := core.CONTEXT.GetDB().Where("uuid = ?", uuid).First(entity)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// find by uuid. if not found panic NotFound error
|
||||
func (this *SpaceDao) CheckByUuid(uuid string) *Space {
|
||||
entity := this.FindByUuid(uuid)
|
||||
if entity == nil {
|
||||
panic(result.NotFound("not found record with uuid = %s", uuid))
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
func (this *SpaceDao) CountByName(name string) int {
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().
|
||||
Model(&Space{}).
|
||||
Where("name = ?", name).
|
||||
Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func (this *SpaceDao) FindByName(name string) *Space {
|
||||
|
||||
var space = &Space{}
|
||||
db := core.CONTEXT.GetDB().Where(&Space{Name: name}).First(space)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return space
|
||||
}
|
||||
|
||||
func (this *SpaceDao) CountByUserUuid(userUuid string) int {
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().
|
||||
Model(&Space{}).
|
||||
Where("user_uuid = ?", userUuid).
|
||||
Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
return int(count)
|
||||
}
|
||||
|
||||
// TODO:
|
||||
func (this *SpaceDao) SelfPage(page int, pageSize int, userUuid string, spaceType string, sortArray []builder.OrderPair) *Pager {
|
||||
|
||||
countSqlTemplate := fmt.Sprintf("SELECT COUNT(*) FROM `%sspace` WHERE uuid IN (SELECT space_uuid FROM `%sspace_member` WHERE user_uuid = ?) AND type = ?", core.TABLE_PREFIX, core.TABLE_PREFIX)
|
||||
if spaceType == SPACE_TYPE_PRIVATE {
|
||||
countSqlTemplate = fmt.Sprintf("SELECT COUNT(*) FROM `%sspace` WHERE user_uuid = ? AND type = ?", core.TABLE_PREFIX)
|
||||
}
|
||||
var count int
|
||||
core.CONTEXT.GetDB().Raw(countSqlTemplate, userUuid, spaceType).Scan(&count)
|
||||
|
||||
orderByString := this.GetSortString(sortArray)
|
||||
if orderByString == "" {
|
||||
orderByString = "uuid"
|
||||
}
|
||||
querySqlTemplate := fmt.Sprintf("SELECT * FROM `%sspace` WHERE uuid IN (SELECT space_uuid FROM `%sspace_member` WHERE user_uuid = ?) AND type = ? ORDER BY ? LIMIT ?,?", core.TABLE_PREFIX, core.TABLE_PREFIX)
|
||||
if spaceType == SPACE_TYPE_PRIVATE {
|
||||
querySqlTemplate = fmt.Sprintf("SELECT * FROM `%sspace` WHERE user_uuid = ? AND type = ? ORDER BY ? LIMIT ?,?", core.TABLE_PREFIX)
|
||||
}
|
||||
var spaces []*Space
|
||||
core.CONTEXT.GetDB().Raw(querySqlTemplate, userUuid, spaceType, orderByString, page*pageSize, pageSize).Scan(&spaces)
|
||||
|
||||
pager := NewPager(page, pageSize, count, spaces)
|
||||
|
||||
return pager
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceDao) Page(page int, pageSize int, spaceType string, name string, sortArray []builder.OrderPair) *Pager {
|
||||
count, spaces := this.PlainPage(page, pageSize, spaceType, name, sortArray)
|
||||
pager := NewPager(page, pageSize, count, spaces)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
func (this *SpaceDao) PlainPage(page int, pageSize int, spaceType string, name string, sortArray []builder.OrderPair) (int, []*Space) {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
if spaceType != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "type = ?", Args: []interface{}{spaceType}})
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "name LIKE ?", Args: []interface{}{"%" + name + "%"}})
|
||||
}
|
||||
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&Space{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var spaces []*Space
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&spaces)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return int(count), spaces
|
||||
}
|
||||
|
||||
func (this *SpaceDao) Create(space *Space) *Space {
|
||||
|
||||
timeUUID, _ := uuid.NewV4()
|
||||
space.Uuid = string(timeUUID.String())
|
||||
space.CreateTime = time.Now()
|
||||
space.UpdateTime = time.Now()
|
||||
space.Sort = time.Now().UnixNano() / 1e6
|
||||
db := core.CONTEXT.GetDB().Create(space)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return space
|
||||
}
|
||||
|
||||
func (this *SpaceDao) Save(space *Space) *Space {
|
||||
|
||||
space.UpdateTime = time.Now()
|
||||
db := core.CONTEXT.GetDB().Save(space)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return space
|
||||
}
|
||||
|
||||
func (this *SpaceDao) UpdateTotalSize(spaceUuid string, totalSize int64) {
|
||||
db := core.CONTEXT.GetDB().Model(&Space{}).Where("uuid = ?", spaceUuid).Update("total_size", totalSize)
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
// handle user page by page.
|
||||
func (this *SpaceDao) PageHandle(fun func(space *Space)) {
|
||||
|
||||
pageSize := 1000
|
||||
sortArray := []builder.OrderPair{
|
||||
{
|
||||
Key: "uuid",
|
||||
Value: DIRECTION_ASC,
|
||||
},
|
||||
}
|
||||
count, _ := this.PlainPage(0, pageSize, "", "", sortArray)
|
||||
if count > 0 {
|
||||
var totalPages = int(math.Ceil(float64(count) / float64(pageSize)))
|
||||
var page int
|
||||
for page = 0; page < totalPages; page++ {
|
||||
_, spaces := this.PlainPage(0, pageSize, "", "", sortArray)
|
||||
for _, space := range spaces {
|
||||
fun(space)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SpaceDao) DeleteByUserUuid(userUuid string) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Where("user_uuid = ?", userUuid).Delete(Space{})
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceDao) Delete(space *Space) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Delete(&space)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
// System cleanup.
|
||||
func (this *SpaceDao) Cleanup() {
|
||||
this.logger.Info("[SpaceDao] clean up. Delete all Space")
|
||||
db := core.CONTEXT.GetDB().Where("uuid is not null").Delete(Space{})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SpaceMemberController struct {
|
||||
BaseController
|
||||
spaceMemberDao *SpaceMemberDao
|
||||
spaceDao *SpaceDao
|
||||
bridgeDao *BridgeDao
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
spaceMemberService *SpaceMemberService
|
||||
}
|
||||
|
||||
func (this *SpaceMemberController) Init() {
|
||||
this.BaseController.Init()
|
||||
|
||||
b := core.CONTEXT.GetBean(this.spaceMemberDao)
|
||||
if b, ok := b.(*SpaceMemberDao); ok {
|
||||
this.spaceMemberDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.bridgeDao)
|
||||
if b, ok := b.(*BridgeDao); ok {
|
||||
this.bridgeDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterDao)
|
||||
if b, ok := b.(*MatterDao); ok {
|
||||
this.matterDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterService)
|
||||
if b, ok := b.(*MatterService); ok {
|
||||
this.matterService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceMemberService)
|
||||
if b, ok := b.(*SpaceMemberService); ok {
|
||||
this.spaceMemberService = b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceMemberController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
|
||||
|
||||
//admin user can create/edit/delete
|
||||
routeMap["/api/space/member/create"] = this.Wrap(this.Create, USER_ROLE_USER)
|
||||
routeMap["/api/space/member/edit"] = this.Wrap(this.Edit, USER_ROLE_USER)
|
||||
routeMap["/api/space/member/delete"] = this.Wrap(this.Delete, USER_ROLE_USER)
|
||||
|
||||
routeMap["/api/space/member/detail"] = this.Wrap(this.Detail, USER_ROLE_USER)
|
||||
routeMap["/api/space/member/mine"] = this.Wrap(this.Mine, USER_ROLE_USER)
|
||||
routeMap["/api/space/member/page"] = this.Wrap(this.Page, USER_ROLE_USER)
|
||||
|
||||
return routeMap
|
||||
}
|
||||
|
||||
func (this *SpaceMemberController) Create(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
spaceUuid := util.ExtractRequestString(request, "spaceUuid")
|
||||
userUuidsStr := util.ExtractRequestString(request, "userUuids")
|
||||
spaceRole := util.ExtractRequestString(request, "role")
|
||||
|
||||
if spaceRole != SPACE_MEMBER_ROLE_READ_ONLY && spaceRole != SPACE_MEMBER_ROLE_READ_WRITE && spaceRole != SPACE_MEMBER_ROLE_ADMIN {
|
||||
panic("spaceRole is not correct")
|
||||
}
|
||||
|
||||
//validate userUuids
|
||||
if userUuidsStr == "" {
|
||||
panic("userUuids is required")
|
||||
}
|
||||
userUuids := strings.Split(userUuidsStr, ",")
|
||||
|
||||
// check operator's permission
|
||||
currentUser := this.checkUser(request)
|
||||
canManage := this.spaceMemberService.canManage(currentUser, spaceUuid)
|
||||
if !canManage {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
//check whether exists.
|
||||
for _, userUuid := range userUuids {
|
||||
spaceMember := this.spaceMemberDao.FindBySpaceUuidAndUserUuid(spaceUuid, userUuid)
|
||||
user := this.userDao.CheckByUuid(userUuid)
|
||||
if spaceMember != nil {
|
||||
panic(result.BadRequestI18n(request, i18n.SpaceMemberExist, user.Username))
|
||||
}
|
||||
}
|
||||
|
||||
//check whether space exists.
|
||||
space := this.spaceDao.CheckByUuid(spaceUuid)
|
||||
|
||||
//check whether exists.
|
||||
for _, userUuid := range userUuids {
|
||||
user := this.userDao.CheckByUuid(userUuid)
|
||||
this.spaceMemberService.CreateMember(space, user, spaceRole)
|
||||
}
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
func (this *SpaceMemberController) Edit(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
spaceRole := util.ExtractRequestString(request, "role")
|
||||
|
||||
if spaceRole != SPACE_MEMBER_ROLE_READ_ONLY && spaceRole != SPACE_MEMBER_ROLE_READ_WRITE && spaceRole != SPACE_MEMBER_ROLE_ADMIN {
|
||||
panic("spaceRole is not correct")
|
||||
}
|
||||
|
||||
spaceMember := this.spaceMemberDao.CheckByUuid(uuid)
|
||||
|
||||
currentUser := this.checkUser(request)
|
||||
canManage := this.spaceMemberService.canManage(currentUser, spaceMember.SpaceUuid)
|
||||
if !canManage {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
spaceMember.Role = spaceRole
|
||||
spaceMember = this.spaceMemberDao.Save(spaceMember)
|
||||
|
||||
return this.Success(spaceMember)
|
||||
}
|
||||
|
||||
func (this *SpaceMemberController) Delete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
|
||||
spaceMember := this.spaceMemberDao.CheckByUuid(uuid)
|
||||
user := this.checkUser(request)
|
||||
canManage := this.spaceMemberService.canManage(user, spaceMember.SpaceUuid)
|
||||
if !canManage {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
this.spaceMemberDao.Delete(spaceMember)
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
|
||||
func (this *SpaceMemberController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := util.ExtractRequestString(request, "uuid")
|
||||
|
||||
spaceMember := this.spaceMemberDao.CheckByUuid(uuid)
|
||||
|
||||
user := this.checkUser(request)
|
||||
|
||||
if spaceMember.UserUuid != user.Uuid {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
return this.Success(spaceMember)
|
||||
|
||||
}
|
||||
|
||||
// find my role in the space.
|
||||
func (this *SpaceMemberController) Mine(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
spaceUuid := util.ExtractRequestString(request, "spaceUuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
spaceMember := this.spaceMemberDao.FindBySpaceUuidAndUserUuid(spaceUuid, user.Uuid)
|
||||
if spaceMember == nil {
|
||||
spaceMember = &SpaceMember{SpaceUuid: spaceUuid, Role: SPACE_MEMBER_GUEST}
|
||||
}
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
spaceMember.Role = SPACE_MEMBER_ROLE_ADMIN
|
||||
}
|
||||
|
||||
return this.Success(spaceMember)
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceMemberController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
page := util.ExtractRequestOptionalInt(request, "page", 0)
|
||||
pageSize := util.ExtractRequestOptionalInt(request, "pageSize", 20)
|
||||
orderCreateTime := util.ExtractRequestOptionalString(request, "orderCreateTime", "")
|
||||
spaceUuid := util.ExtractRequestString(request, "spaceUuid")
|
||||
|
||||
user := this.checkUser(request)
|
||||
canRead := this.spaceMemberService.canRead(user, spaceUuid)
|
||||
if !canRead {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
sortArray := []builder.OrderPair{
|
||||
{
|
||||
Key: "create_time",
|
||||
Value: orderCreateTime,
|
||||
},
|
||||
}
|
||||
|
||||
pager := this.spaceMemberDao.Page(page, pageSize, spaceUuid, sortArray)
|
||||
|
||||
//fill the space's user. FIXME: user better way to get User.
|
||||
if pager != nil {
|
||||
for _, spaceMember := range pager.Data.([]*SpaceMember) {
|
||||
spaceMember.User = this.userDao.FindByUuid(spaceMember.UserUuid)
|
||||
}
|
||||
}
|
||||
|
||||
return this.Success(pager)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/builder"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SpaceMemberDao struct {
|
||||
BaseDao
|
||||
}
|
||||
|
||||
// find by uuid. if not found return nil.
|
||||
func (this *SpaceMemberDao) FindByUuid(uuid string) *SpaceMember {
|
||||
var entity = &SpaceMember{}
|
||||
db := core.CONTEXT.GetDB().Where("uuid = ?", uuid).First(entity)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// find by uuid. if not found panic NotFound error
|
||||
func (this *SpaceMemberDao) CheckByUuid(uuid string) *SpaceMember {
|
||||
entity := this.FindByUuid(uuid)
|
||||
if entity == nil {
|
||||
panic(result.NotFound("not found record with uuid = %s", uuid))
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
// find by spaceUuid and userUuid. if not found return nil.
|
||||
func (this *SpaceMemberDao) FindBySpaceUuidAndUserUuid(spaceUuid string, userUuid string) *SpaceMember {
|
||||
var entity = &SpaceMember{}
|
||||
db := core.CONTEXT.GetDB().Where("space_uuid = ? AND user_uuid = ?", spaceUuid, userUuid).First(entity)
|
||||
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
func (this *SpaceMemberDao) Page(page int, pageSize int, spaceUuid string, sortArray []builder.OrderPair) *Pager {
|
||||
|
||||
count, spaceMembers := this.PlainPage(page, pageSize, spaceUuid, sortArray)
|
||||
pager := NewPager(page, pageSize, count, spaceMembers)
|
||||
|
||||
return pager
|
||||
}
|
||||
|
||||
func (this *SpaceMemberDao) PlainPage(page int, pageSize int, spaceUuid string, sortArray []builder.OrderPair) (int, []*SpaceMember) {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
if spaceUuid != "" {
|
||||
wp = wp.And(&builder.WherePair{Query: "space_uuid = ?", Args: []interface{}{spaceUuid}})
|
||||
}
|
||||
|
||||
var conditionDB *gorm.DB
|
||||
conditionDB = core.CONTEXT.GetDB().Model(&SpaceMember{}).Where(wp.Query, wp.Args...)
|
||||
|
||||
var count int64 = 0
|
||||
db := conditionDB.Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
var spaceMembers []*SpaceMember
|
||||
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&spaceMembers)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return int(count), spaceMembers
|
||||
}
|
||||
|
||||
func (this *SpaceMemberDao) Create(spaceMember *SpaceMember) *SpaceMember {
|
||||
|
||||
timeUUID, _ := uuid.NewV4()
|
||||
spaceMember.Uuid = string(timeUUID.String())
|
||||
spaceMember.CreateTime = time.Now()
|
||||
spaceMember.UpdateTime = time.Now()
|
||||
spaceMember.Sort = time.Now().UnixNano() / 1e6
|
||||
db := core.CONTEXT.GetDB().Create(spaceMember)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return spaceMember
|
||||
}
|
||||
|
||||
func (this *SpaceMemberDao) Save(spaceMember *SpaceMember) *SpaceMember {
|
||||
|
||||
spaceMember.UpdateTime = time.Now()
|
||||
db := core.CONTEXT.GetDB().Save(spaceMember)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
return spaceMember
|
||||
}
|
||||
|
||||
func (this *SpaceMemberDao) Delete(spaceMember *SpaceMember) {
|
||||
|
||||
db := core.CONTEXT.GetDB().Delete(&spaceMember)
|
||||
this.PanicError(db.Error)
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceMemberDao) DeleteBySpaceUuid(spaceUuid string) {
|
||||
|
||||
var wp = &builder.WherePair{}
|
||||
|
||||
wp = wp.And(&builder.WherePair{Query: "space_uuid = ?", Args: []interface{}{spaceUuid}})
|
||||
|
||||
db := core.CONTEXT.GetDB().Where(wp.Query, wp.Args).Delete(SpaceMember{})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
func (this *SpaceMemberDao) CountBySpaceUuid(spaceUuid string) int {
|
||||
var count int64
|
||||
db := core.CONTEXT.GetDB().
|
||||
Model(&SpaceMember{}).
|
||||
Where("space_uuid = ?", spaceUuid).
|
||||
Count(&count)
|
||||
this.PanicError(db.Error)
|
||||
return int(count)
|
||||
}
|
||||
|
||||
// System cleanup.
|
||||
func (this *SpaceMemberDao) Cleanup() {
|
||||
this.logger.Info("[SpaceMemberDao] clean up. Delete all SpaceMember")
|
||||
db := core.CONTEXT.GetDB().Where("uuid is not null").Delete(SpaceMember{})
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//guest member
|
||||
SPACE_MEMBER_GUEST = "GUEST"
|
||||
//read only member
|
||||
SPACE_MEMBER_ROLE_READ_ONLY = "READ_ONLY"
|
||||
//read write member
|
||||
SPACE_MEMBER_ROLE_READ_WRITE = "READ_WRITE"
|
||||
//admin member
|
||||
SPACE_MEMBER_ROLE_ADMIN = "ADMIN"
|
||||
)
|
||||
|
||||
/**
|
||||
* space member
|
||||
*/
|
||||
type SpaceMember struct {
|
||||
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
|
||||
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
|
||||
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
|
||||
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
SpaceUuid string `json:"spaceUuid" gorm:"type:char(36);index:idx_space_member_su"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36);index:idx_space_member_uu"`
|
||||
Role string `json:"role" gorm:"type:varchar(45)"`
|
||||
User *User `json:"user" gorm:"-"`
|
||||
}
|
||||