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 | ||
|
|
6626afa6b6 | ||
|
|
6d35063027 |
@@ -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
|
||||
|
||||
|
||||
@@ -51,6 +54,75 @@ c. 在github上发布新版本。
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# use golang 1.17
|
||||
FROM golang:1.17
|
||||
# use golang 1.22.5
|
||||
FROM golang:1.22.5
|
||||
|
||||
# maintainer. Author's name and email.
|
||||
MAINTAINER eyeblue "eyebluecn@126.com"
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
[English Version](https://tank-doc.eyeblue.cn/en)
|
||||
|
||||
# 蓝眼云盘(3.1.6)
|
||||
# 蓝眼云盘(4.2.0)
|
||||
[在线Demo](https://tanker.eyeblue.cn) (体验账号: demo 密码:123456)
|
||||
|
||||
蓝眼云盘是蓝眼开源系列代表作品之一,致力于打造精致,优雅,简约的云盘。核心功能如下:
|
||||
1. 文件列表
|
||||
2. 文件/文件夹上传 + 拖拽上传
|
||||
2. 文件/文件夹上传 + 拖拽上传 + 分片/断点续传
|
||||
3. 共享空间
|
||||
3. 文件分享
|
||||
4. 文件监控统计
|
||||
5. 回收站
|
||||
@@ -17,6 +18,7 @@
|
||||
9. [在线预览及自定义配置预览引擎](https://tank-doc.eyeblue.cn/advance/preview.html)
|
||||
10. 支持Sqlite和Mysql数据库用以存储文件元信息,Sqlite可以做到开箱即用。
|
||||
|
||||
|
||||
[更多内容请移步至文档](https://tank-doc.eyeblue.cn/)
|
||||
|
||||
### 软件截图
|
||||
@@ -36,17 +38,27 @@
|
||||
|
||||
## [安装文档](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版本。
|
||||
|
||||

|
||||

|
||||
|
||||
群号:23156361
|
||||
群号:44754005
|
||||
|
||||
|
||||
### Support
|
||||
|
||||
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 208 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,28 +1,29 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.b03c51fb.css",
|
||||
"main.js": "/static/js/main.b94b1947.js",
|
||||
"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.20ceb38d310075aa4c3cdaae210afb65.svg",
|
||||
"static/media/psd.svg": "/static/media/psd.bf2ac5411c0132f292787f45855934f2.svg",
|
||||
"static/media/xls.svg": "/static/media/xls.02895077484083ddc5346ab720cd8d1b.svg",
|
||||
"static/media/ppt.svg": "/static/media/ppt.7dc0cfc7fdf0e6f66fb94c81fb34575f.svg",
|
||||
"static/media/doc.svg": "/static/media/doc.01619bc74278a86447e699223663ad64.svg",
|
||||
"static/media/pdf.svg": "/static/media/pdf.eba1e4d316279fc69682dabae6fe6fa4.svg",
|
||||
"static/media/video.svg": "/static/media/video.adecb1d58a645ca8d1b1bffad4dbe777.svg",
|
||||
"static/media/default-skin.svg": "/static/media/default-skin.f64c3af3d0d25b9e4e00.svg",
|
||||
"static/media/text.svg": "/static/media/text.8000463985257af8534ee29db23e1006.svg",
|
||||
"static/media/file.svg": "/static/media/file.074f42545edfc31c849bdddb013ad8b2.svg",
|
||||
"static/media/audio.svg": "/static/media/audio.e7112b210bfaa8d0a2ab69f74a66eea4.svg",
|
||||
"static/media/image.svg": "/static/media/image.7660bae05269b3c8f2cdf669f04aef3b.svg",
|
||||
"static/media/folder.svg": "/static/media/folder.0bdd7430280c98bf4970b6af5a061c2c.svg",
|
||||
"static/media/archive.svg": "/static/media/archive.684c1e42f233aa9d53a8910d4fee091e.svg",
|
||||
"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",
|
||||
"main.b03c51fb.css.map": "/static/css/main.b03c51fb.css.map",
|
||||
"main.b94b1947.js.map": "/static/js/main.b94b1947.js.map"
|
||||
"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/css/main.b03c51fb.css",
|
||||
"static/js/main.b94b1947.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><script defer="defer" src="/static/js/main.b94b1947.js"></script><link href="/static/css/main.b03c51fb.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run EyeblueTank.</noscript><div id="root"></div></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>
|
||||
@@ -10,14 +10,6 @@ object-assign
|
||||
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. */
|
||||
@@ -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 |
@@ -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 @@
|
||||
<?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 |
@@ -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 |
@@ -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,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 |
@@ -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 |
@@ -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,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="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 |
@@ -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="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 |
@@ -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 |
@@ -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 |
@@ -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="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 |
@@ -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 |
@@ -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 |
@@ -11,7 +11,7 @@
|
||||
@REM prepare the variables.
|
||||
|
||||
@REM version name
|
||||
SET VERSION_NAME=tank-3.1.6
|
||||
SET VERSION_NAME=tank-4.2.0
|
||||
ECHO VERSION_NAME: %VERSION_NAME%
|
||||
@REM golang proxy
|
||||
SET GOPROXY=https://goproxy.cn
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
###########################################################################
|
||||
#
|
||||
@@ -9,7 +10,7 @@
|
||||
#prepare the variables.
|
||||
|
||||
# version name
|
||||
VERSION_NAME=tank-3.1.6
|
||||
VERSION_NAME=tank-4.2.0
|
||||
echo "VERSION_NAME: ${VERSION_NAME}"
|
||||
# golang proxy
|
||||
GOPROXY=https://goproxy.cn
|
||||
|
||||
@@ -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
|
||||
@@ -11,10 +11,10 @@ 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.6"
|
||||
VERSION = "4.2.0"
|
||||
)
|
||||
|
||||
type Config interface {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ type Dashboard struct {
|
||||
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.
|
||||
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" {
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ type DownloadToken struct {
|
||||
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_download_token_mu"` //index should unique globally.
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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{})
|
||||
|
||||
@@ -24,5 +24,5 @@ type ImageCache struct {
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"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
|
||||
@@ -78,7 +78,11 @@ func (this *InstallController) Init() {
|
||||
&Preference{},
|
||||
&Session{},
|
||||
&Share{},
|
||||
&Space{},
|
||||
&SpaceMember{},
|
||||
&UploadToken{},
|
||||
&UploadSession{},
|
||||
&UploadChunk{},
|
||||
&User{},
|
||||
}
|
||||
|
||||
@@ -266,7 +270,7 @@ 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)
|
||||
@@ -324,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)
|
||||
@@ -342,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)
|
||||
@@ -368,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()
|
||||
@@ -378,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)
|
||||
@@ -422,7 +442,7 @@ 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")
|
||||
|
||||
@@ -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"
|
||||
"gorm.io/gorm"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -175,6 +176,68 @@ func (this *MatterDao) CountByUserUuidAndPuuidAndDirAndName(userUuid string, puu
|
||||
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 {
|
||||
|
||||
var matter = &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 + "%"}})
|
||||
}
|
||||
@@ -320,18 +453,19 @@ func (this *MatterDao) PlainPage(
|
||||
|
||||
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,7 +519,7 @@ 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).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,7 +605,7 @@ 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.
|
||||
@@ -459,7 +614,7 @@ func (this *MatterDao) SoftDelete(matter *Matter) {
|
||||
|
||||
}
|
||||
|
||||
//recovery a file
|
||||
// recovery a file
|
||||
func (this *MatterDao) Recovery(matter *Matter) {
|
||||
|
||||
//recovery from db.
|
||||
@@ -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{})
|
||||
|
||||
@@ -34,9 +34,10 @@ type Matter struct {
|
||||
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.
|
||||
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"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null"`
|
||||
//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)"`
|
||||
@@ -44,39 +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_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)
|
||||
@@ -84,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 == "" {
|
||||
@@ -127,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)
|
||||
@@ -144,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)
|
||||
|
||||
@@ -19,6 +19,7 @@ type Preference struct {
|
||||
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"`
|
||||
@@ -32,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
|
||||
|
||||
@@ -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")
|
||||
@@ -375,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)
|
||||
}
|
||||
|
||||
@@ -33,6 +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:"-"`
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//@Service
|
||||
// @Service
|
||||
type ShareService struct {
|
||||
BaseBean
|
||||
shareDao *ShareDao
|
||||
@@ -74,18 +74,13 @@ 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"))
|
||||
}
|
||||
@@ -114,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.
|
||||
@@ -140,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:"-"`
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
)
|
||||
|
||||
// @Service
|
||||
type SpaceMemberService struct {
|
||||
BaseBean
|
||||
spaceMemberDao *SpaceMemberDao
|
||||
matterDao *MatterDao
|
||||
bridgeDao *BridgeDao
|
||||
userDao *UserDao
|
||||
}
|
||||
|
||||
func (this *SpaceMemberService) Init() {
|
||||
this.BaseBean.Init()
|
||||
|
||||
b := core.CONTEXT.GetBean(this.spaceMemberDao)
|
||||
if b, ok := b.(*SpaceMemberDao); ok {
|
||||
this.spaceMemberDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterDao)
|
||||
if b, ok := b.(*MatterDao); ok {
|
||||
this.matterDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.bridgeDao)
|
||||
if b, ok := b.(*BridgeDao); ok {
|
||||
this.bridgeDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.userDao)
|
||||
if b, ok := b.(*UserDao); ok {
|
||||
this.userDao = b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceMemberService) Detail(uuid string) *SpaceMember {
|
||||
|
||||
spaceMember := this.spaceMemberDao.CheckByUuid(uuid)
|
||||
|
||||
return spaceMember
|
||||
}
|
||||
|
||||
// create space
|
||||
func (this *SpaceMemberService) CreateMember(space *Space, user *User, spaceRole string) *SpaceMember {
|
||||
|
||||
spaceMember := &SpaceMember{
|
||||
SpaceUuid: space.Uuid,
|
||||
UserUuid: user.Uuid,
|
||||
Role: spaceRole,
|
||||
}
|
||||
|
||||
spaceMember = this.spaceMemberDao.Create(spaceMember)
|
||||
|
||||
return spaceMember
|
||||
|
||||
}
|
||||
|
||||
// 当前用户对于此空间,是否有管理权限。
|
||||
func (this *SpaceMemberService) canManage(user *User, spaceUuid string) bool {
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
return true
|
||||
}
|
||||
if user.SpaceUuid == spaceUuid {
|
||||
return true
|
||||
}
|
||||
|
||||
//only space's admin can add member.
|
||||
spaceMember := this.spaceMemberDao.FindBySpaceUuidAndUserUuid(spaceUuid, user.Uuid)
|
||||
return this.canManageBySpaceMember(user, spaceMember)
|
||||
}
|
||||
|
||||
// 当前用户对于此空间,是否有可读权限。
|
||||
func (this *SpaceMemberService) canRead(user *User, spaceUuid string) bool {
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
return true
|
||||
}
|
||||
if user.SpaceUuid == spaceUuid {
|
||||
return true
|
||||
}
|
||||
|
||||
//only space's admin can add member.
|
||||
spaceMember := this.spaceMemberDao.FindBySpaceUuidAndUserUuid(spaceUuid, user.Uuid)
|
||||
return this.canReadBySpaceMember(user, spaceMember)
|
||||
}
|
||||
|
||||
// 当前用户对于此空间,是否有可写权限。
|
||||
func (this *SpaceMemberService) canWrite(user *User, spaceUuid string) bool {
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
return true
|
||||
}
|
||||
if user.SpaceUuid == spaceUuid {
|
||||
return true
|
||||
}
|
||||
|
||||
//only space's admin can add member.
|
||||
spaceMember := this.spaceMemberDao.FindBySpaceUuidAndUserUuid(spaceUuid, user.Uuid)
|
||||
return this.canWriteBySpaceMember(user, spaceMember)
|
||||
}
|
||||
|
||||
// 当前用户对于此空间,是否有管理权限。
|
||||
func (this *SpaceMemberService) canManageBySpaceMember(user *User, member *SpaceMember) bool {
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
return true
|
||||
}
|
||||
|
||||
//only space's admin can add member.
|
||||
if member != nil && member.Role == SPACE_MEMBER_ROLE_ADMIN {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 当前用户对于此空间,是否有可读权限。
|
||||
func (this *SpaceMemberService) canReadBySpaceMember(user *User, member *SpaceMember) bool {
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
return true
|
||||
}
|
||||
|
||||
//only space's admin can add member.
|
||||
if member != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 当前用户对于此空间,是否有科协权限。
|
||||
func (this *SpaceMemberService) canWriteBySpaceMember(user *User, member *SpaceMember) bool {
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
return true
|
||||
}
|
||||
|
||||
//only space's admin can add member.
|
||||
if member != nil && (member.Role == SPACE_MEMBER_ROLE_ADMIN || member.Role == SPACE_MEMBER_ROLE_READ_WRITE) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//private space
|
||||
SPACE_TYPE_PRIVATE = "PRIVATE"
|
||||
//group shared space.
|
||||
SPACE_TYPE_SHARED = "SHARED"
|
||||
)
|
||||
|
||||
/**
|
||||
* shared space
|
||||
*/
|
||||
type Space 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'"`
|
||||
Name string `json:"name" gorm:"type:varchar(100) not null;unique"`
|
||||
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
|
||||
SizeLimit int64 `json:"sizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
TotalSizeLimit int64 `json:"totalSizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
TotalSize int64 `json:"totalSize" gorm:"type:bigint(20) not null;default:0"`
|
||||
Type string `json:"type" gorm:"type:varchar(45)"`
|
||||
User *User `json:"user" gorm:"-"`
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/i18n"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// @Service
|
||||
type SpaceService struct {
|
||||
BaseBean
|
||||
spaceDao *SpaceDao
|
||||
spaceMemberService *SpaceMemberService
|
||||
matterDao *MatterDao
|
||||
bridgeDao *BridgeDao
|
||||
userDao *UserDao
|
||||
}
|
||||
|
||||
func (this *SpaceService) Init() {
|
||||
this.BaseBean.Init()
|
||||
|
||||
b := core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = 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.bridgeDao)
|
||||
if b, ok := b.(*BridgeDao); ok {
|
||||
this.bridgeDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.userDao)
|
||||
if b, ok := b.(*UserDao); ok {
|
||||
this.userDao = b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *SpaceService) Detail(uuid string) *Space {
|
||||
|
||||
space := this.spaceDao.CheckByUuid(uuid)
|
||||
|
||||
return space
|
||||
}
|
||||
|
||||
// create space
|
||||
func (this *SpaceService) CreateSpace(
|
||||
request *http.Request,
|
||||
name string,
|
||||
user *User,
|
||||
sizeLimit int64,
|
||||
totalSizeLimit int64,
|
||||
spaceType string) *Space {
|
||||
|
||||
userUuid := ""
|
||||
//validation work.
|
||||
if m, _ := regexp.MatchString(USERNAME_PATTERN, name); !m {
|
||||
panic(result.BadRequestI18n(request, i18n.SpaceNameError))
|
||||
}
|
||||
|
||||
if spaceType == SPACE_TYPE_PRIVATE {
|
||||
if user == nil {
|
||||
panic("private space requires user.")
|
||||
}
|
||||
|
||||
userUuid = user.Uuid
|
||||
if this.spaceDao.CountByUserUuid(userUuid) > 0 {
|
||||
panic(result.BadRequestI18n(request, i18n.SpaceExclusive, name))
|
||||
}
|
||||
|
||||
} else if spaceType == SPACE_TYPE_SHARED {
|
||||
|
||||
} else {
|
||||
panic("Not supported spaceType:" + spaceType)
|
||||
}
|
||||
|
||||
if this.spaceDao.CountByName(name) > 0 {
|
||||
panic(result.BadRequestI18n(request, i18n.SpaceNameExist, name))
|
||||
}
|
||||
|
||||
space := &Space{
|
||||
Name: name,
|
||||
UserUuid: userUuid,
|
||||
SizeLimit: sizeLimit,
|
||||
TotalSizeLimit: totalSizeLimit,
|
||||
TotalSize: 0,
|
||||
Type: spaceType,
|
||||
}
|
||||
|
||||
space = this.spaceDao.Create(space)
|
||||
|
||||
return space
|
||||
|
||||
}
|
||||
|
||||
// checkout a adminAble space.
|
||||
func (this *SpaceService) CheckAdminAbleByUuid(request *http.Request, user *User, spaceUuid string) *Space {
|
||||
space := this.spaceDao.CheckByUuid(spaceUuid)
|
||||
if space.Type == SPACE_TYPE_PRIVATE && user.Uuid == space.UserUuid {
|
||||
return space
|
||||
}
|
||||
|
||||
manage := this.spaceMemberService.canManage(user, spaceUuid)
|
||||
if !manage {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
return space
|
||||
}
|
||||
|
||||
// checkout a writable space.
|
||||
func (this *SpaceService) CheckWritableByUuid(request *http.Request, user *User, spaceUuid string) *Space {
|
||||
space := this.spaceDao.CheckByUuid(spaceUuid)
|
||||
if space.Type == SPACE_TYPE_PRIVATE && user.Uuid == space.UserUuid {
|
||||
return space
|
||||
}
|
||||
|
||||
writable := this.spaceMemberService.canWrite(user, spaceUuid)
|
||||
if !writable {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
return space
|
||||
}
|
||||
|
||||
// checkout a readable space.
|
||||
func (this *SpaceService) CheckReadableByUuid(request *http.Request, user *User, spaceUuid string) *Space {
|
||||
space := this.spaceDao.CheckByUuid(spaceUuid)
|
||||
if space.Type == SPACE_TYPE_PRIVATE && user.Uuid == space.UserUuid {
|
||||
return space
|
||||
}
|
||||
|
||||
manage := this.spaceMemberService.canRead(user, spaceUuid)
|
||||
if !manage {
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
return space
|
||||
}
|
||||
|
||||
// edit space's info
|
||||
func (this *SpaceService) Edit(request *http.Request, user *User, spaceUuid string, sizeLimit int64, totalSizeLimit int64) *Space {
|
||||
space := this.CheckAdminAbleByUuid(request, user, spaceUuid)
|
||||
|
||||
if sizeLimit < 0 && sizeLimit != -1 {
|
||||
panic("sizeLimit cannot be negative expect -1.")
|
||||
}
|
||||
|
||||
if totalSizeLimit < 0 && totalSizeLimit != -1 {
|
||||
panic("totalSizeLimit cannot be negative expect -1.")
|
||||
}
|
||||
|
||||
space.SizeLimit = sizeLimit
|
||||
space.TotalSizeLimit = totalSizeLimit
|
||||
space = this.spaceDao.Save(space)
|
||||
|
||||
return space
|
||||
}
|
||||
@@ -8,14 +8,16 @@ import (
|
||||
)
|
||||
|
||||
// system tasks service
|
||||
//@Service
|
||||
// @Service
|
||||
type TaskService struct {
|
||||
BaseBean
|
||||
footprintService *FootprintService
|
||||
dashboardService *DashboardService
|
||||
preferenceService *PreferenceService
|
||||
matterService *MatterService
|
||||
chunkService *ChunkService
|
||||
userDao *UserDao
|
||||
spaceDao *SpaceDao
|
||||
|
||||
//whether scan task is running
|
||||
scanTaskRunning bool
|
||||
@@ -44,15 +46,25 @@ func (this *TaskService) Init() {
|
||||
if b, ok := b.(*MatterService); ok {
|
||||
this.matterService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.chunkService)
|
||||
if b, ok := b.(*ChunkService); ok {
|
||||
this.chunkService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.userDao)
|
||||
if b, ok := b.(*UserDao); ok {
|
||||
this.userDao = b
|
||||
}
|
||||
b = core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = b
|
||||
}
|
||||
|
||||
this.scanTaskRunning = false
|
||||
}
|
||||
|
||||
//init the clean footprint task.
|
||||
// init the clean footprint task.
|
||||
func (this *TaskService) InitCleanFootprintTask() {
|
||||
|
||||
//use standard cron expression. 5 fields. ()
|
||||
@@ -65,7 +77,7 @@ func (this *TaskService) InitCleanFootprintTask() {
|
||||
this.logger.Info("[cron job] Every day 00:10 delete Footprint data of 8 days ago.")
|
||||
}
|
||||
|
||||
//init the elt task.
|
||||
// init the elt task.
|
||||
func (this *TaskService) InitEtlTask() {
|
||||
|
||||
expression := "5 0 * * *"
|
||||
@@ -77,7 +89,7 @@ func (this *TaskService) InitEtlTask() {
|
||||
this.logger.Info("[cron job] Everyday 00:05 ETL dashboard data.")
|
||||
}
|
||||
|
||||
//init the clean deleted matters task.
|
||||
// init the clean deleted matters task.
|
||||
func (this *TaskService) InitCleanDeletedMattersTask() {
|
||||
|
||||
expression := "0 1 * * *"
|
||||
@@ -89,7 +101,19 @@ func (this *TaskService) InitCleanDeletedMattersTask() {
|
||||
this.logger.Info("[cron job] Everyday 01:00 Clean deleted matters.")
|
||||
}
|
||||
|
||||
//scan task.
|
||||
// init the clean chunk sessions task.
|
||||
func (this *TaskService) InitCleanExpiredChunkSessions() {
|
||||
|
||||
expression := "0 2 * * *"
|
||||
cronJob := cron.New()
|
||||
_, err := cronJob.AddFunc(expression, this.chunkService.CleanExpiredSessions)
|
||||
core.PanicError(err)
|
||||
cronJob.Start()
|
||||
|
||||
this.logger.Info("[cron job] Everyday 02:00 Clean expired chunk sessions.")
|
||||
}
|
||||
|
||||
// scan task.
|
||||
func (this *TaskService) doScanTask() {
|
||||
|
||||
if this.scanTaskRunning {
|
||||
@@ -121,31 +145,41 @@ func (this *TaskService) doScanTask() {
|
||||
|
||||
if scanConfig.Scope == SCAN_SCOPE_ALL {
|
||||
//scan all user's root folder.
|
||||
this.userDao.PageHandle("", "", func(user *User) {
|
||||
this.spaceDao.PageHandle(func(space *Space) {
|
||||
|
||||
core.RunWithRecovery(func() {
|
||||
|
||||
this.matterService.DeleteByPhysics(request, user)
|
||||
this.matterService.ScanPhysics(request, user)
|
||||
this.logger.Info("scan spaceName = %s", space.Name)
|
||||
|
||||
//find user by space
|
||||
user := this.userDao.FindByUuid(space.UserUuid)
|
||||
if user == nil {
|
||||
user = this.userDao.FindAnAdmin()
|
||||
}
|
||||
this.matterService.DeleteByPhysics(request, user, space)
|
||||
this.matterService.ScanPhysics(request, user, space)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
} else if scanConfig.Scope == SCAN_SCOPE_CUSTOM {
|
||||
//scan custom user's folder.
|
||||
|
||||
for _, username := range scanConfig.Usernames {
|
||||
user := this.userDao.FindByUsername(username)
|
||||
if user == nil {
|
||||
this.logger.Error("username = %s not exist.", username)
|
||||
for _, spaceName := range scanConfig.SpaceNames {
|
||||
space := this.spaceDao.FindByName(spaceName)
|
||||
if space == nil {
|
||||
this.logger.Error("name = %s not exist.", spaceName)
|
||||
} else {
|
||||
this.logger.Info("scan custom user folder. username = %s", username)
|
||||
this.logger.Info("scan custom user folder. spaceName = %s", spaceName)
|
||||
|
||||
core.RunWithRecovery(func() {
|
||||
|
||||
this.matterService.DeleteByPhysics(request, user)
|
||||
this.matterService.ScanPhysics(request, user)
|
||||
//find user by space
|
||||
user := this.userDao.FindByUuid(space.UserUuid)
|
||||
if user == nil {
|
||||
user = this.userDao.FindAnAdmin()
|
||||
}
|
||||
this.matterService.DeleteByPhysics(request, user, space)
|
||||
this.matterService.ScanPhysics(request, user, space)
|
||||
|
||||
})
|
||||
|
||||
@@ -155,7 +189,7 @@ func (this *TaskService) doScanTask() {
|
||||
|
||||
}
|
||||
|
||||
//init the scan task.
|
||||
// init the scan task.
|
||||
func (this *TaskService) InitScanTask() {
|
||||
|
||||
if this.scanTaskCron != nil {
|
||||
@@ -195,6 +229,9 @@ func (this *TaskService) Bootstrap() {
|
||||
//load the clean deleted matters task.
|
||||
this.InitCleanDeletedMattersTask()
|
||||
|
||||
//load the clean chunk sessions task.
|
||||
this.InitCleanExpiredChunkSessions()
|
||||
|
||||
//load the scan task.
|
||||
this.InitScanTask()
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ type UserController struct {
|
||||
BaseController
|
||||
preferenceService *PreferenceService
|
||||
userService *UserService
|
||||
spaceDao *SpaceDao
|
||||
spaceService *SpaceService
|
||||
matterService *MatterService
|
||||
}
|
||||
|
||||
@@ -32,6 +34,14 @@ func (this *UserController) Init() {
|
||||
this.userService = 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.matterService)
|
||||
if b, ok := b.(*MatterService); ok {
|
||||
this.matterService = b
|
||||
@@ -54,6 +64,7 @@ func (this *UserController) RegisterRoutes() map[string]func(writer http.Respons
|
||||
routeMap["/api/user/change/password"] = this.Wrap(this.ChangePassword, USER_ROLE_USER)
|
||||
routeMap["/api/user/reset/password"] = this.Wrap(this.ResetPassword, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/user/page"] = this.Wrap(this.Page, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/user/search"] = this.Wrap(this.Search, USER_ROLE_USER)
|
||||
routeMap["/api/user/toggle/status"] = this.Wrap(this.ToggleStatus, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/user/transfiguration"] = this.Wrap(this.Transfiguration, USER_ROLE_ADMINISTRATOR)
|
||||
routeMap["/api/user/scan"] = this.Wrap(this.Scan, USER_ROLE_ADMINISTRATOR)
|
||||
@@ -96,7 +107,7 @@ func (this *UserController) innerLogin(writer http.ResponseWriter, request *http
|
||||
this.userDao.Save(user)
|
||||
}
|
||||
|
||||
//login by username and password
|
||||
// login by username and password
|
||||
func (this *UserController) Login(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
username := request.FormValue("username")
|
||||
@@ -116,10 +127,14 @@ func (this *UserController) Login(writer http.ResponseWriter, request *http.Requ
|
||||
}
|
||||
this.innerLogin(writer, request, user)
|
||||
|
||||
//append the space info.
|
||||
space := this.spaceDao.FindByUuid(user.SpaceUuid)
|
||||
user.Space = space
|
||||
|
||||
return this.Success(user)
|
||||
}
|
||||
|
||||
//login by authentication.
|
||||
// login by authentication.
|
||||
func (this *UserController) AuthenticationLogin(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
authentication := request.FormValue("authentication")
|
||||
@@ -140,13 +155,18 @@ func (this *UserController) AuthenticationLogin(writer http.ResponseWriter, requ
|
||||
return this.Success(user)
|
||||
}
|
||||
|
||||
//fetch current user's info.
|
||||
// fetch current user's info.
|
||||
func (this *UserController) Info(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
user := this.checkUser(request)
|
||||
|
||||
//append the space info.
|
||||
space := this.spaceDao.FindByUuid(user.SpaceUuid)
|
||||
user.Space = space
|
||||
|
||||
return this.Success(user)
|
||||
}
|
||||
|
||||
//register by username and password. After registering, will auto login.
|
||||
// register by username and password. After registering, will auto login.
|
||||
func (this *UserController) Register(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
username := request.FormValue("username")
|
||||
@@ -169,15 +189,7 @@ func (this *UserController) Register(writer http.ResponseWriter, request *http.R
|
||||
panic(result.BadRequestI18n(request, i18n.UsernameExist, username))
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Role: USER_ROLE_USER,
|
||||
Username: username,
|
||||
Password: util.GetBcrypt(password),
|
||||
TotalSizeLimit: preference.DefaultTotalSizeLimit,
|
||||
Status: USER_STATUS_OK,
|
||||
}
|
||||
|
||||
user = this.userDao.Create(user)
|
||||
user := this.userService.CreateUser(request, username, -1, preference.DefaultTotalSizeLimit, password, USER_ROLE_USER)
|
||||
|
||||
//auto login
|
||||
this.innerLogin(writer, request, user)
|
||||
@@ -190,32 +202,11 @@ func (this *UserController) Create(writer http.ResponseWriter, request *http.Req
|
||||
username := request.FormValue("username")
|
||||
password := request.FormValue("password")
|
||||
role := request.FormValue("role")
|
||||
sizeLimitStr := request.FormValue("sizeLimit")
|
||||
totalSizeLimitStr := request.FormValue("totalSizeLimit")
|
||||
|
||||
//only admin can edit user's sizeLimit
|
||||
var sizeLimit int64 = 0
|
||||
if sizeLimitStr == "" {
|
||||
panic("user's limit size is required")
|
||||
} else {
|
||||
intSizeLimit, err := strconv.Atoi(sizeLimitStr)
|
||||
if err != nil {
|
||||
this.PanicError(err)
|
||||
}
|
||||
sizeLimit = int64(intSizeLimit)
|
||||
}
|
||||
|
||||
var totalSizeLimit int64 = 0
|
||||
if totalSizeLimitStr == "" {
|
||||
panic("user's total limit size is required")
|
||||
} else {
|
||||
intTotalSizeLimit, err := strconv.Atoi(totalSizeLimitStr)
|
||||
if err != nil {
|
||||
this.PanicError(err)
|
||||
}
|
||||
totalSizeLimit = int64(intTotalSizeLimit)
|
||||
}
|
||||
sizeLimit := util.ExtractRequestInt64(request, "sizeLimit")
|
||||
totalSizeLimit := util.ExtractRequestInt64(request, "totalSizeLimit")
|
||||
|
||||
//validation work.
|
||||
if m, _ := regexp.MatchString(USERNAME_PATTERN, username); !m {
|
||||
panic(result.BadRequestI18n(request, i18n.UsernameError))
|
||||
}
|
||||
@@ -227,21 +218,16 @@ func (this *UserController) Create(writer http.ResponseWriter, request *http.Req
|
||||
if this.userDao.CountByUsername(username) > 0 {
|
||||
panic(result.BadRequestI18n(request, i18n.UsernameExist, username))
|
||||
}
|
||||
if this.spaceDao.CountByName(username) > 0 {
|
||||
panic(result.BadRequestI18n(request, i18n.SpaceNameExist, username))
|
||||
}
|
||||
|
||||
//check user role.
|
||||
if role != USER_ROLE_USER && role != USER_ROLE_ADMINISTRATOR {
|
||||
role = USER_ROLE_USER
|
||||
panic(result.BadRequestI18n(request, i18n.UserRoleError))
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: username,
|
||||
Password: util.GetBcrypt(password),
|
||||
Role: role,
|
||||
SizeLimit: sizeLimit,
|
||||
TotalSizeLimit: totalSizeLimit,
|
||||
Status: USER_STATUS_OK,
|
||||
}
|
||||
|
||||
user = this.userDao.Create(user)
|
||||
user := this.userService.CreateUser(request, username, sizeLimit, totalSizeLimit, password, role)
|
||||
|
||||
return this.Success(user)
|
||||
}
|
||||
@@ -250,54 +236,50 @@ func (this *UserController) Edit(writer http.ResponseWriter, request *http.Reque
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
avatarUrl := request.FormValue("avatarUrl")
|
||||
sizeLimitStr := request.FormValue("sizeLimit")
|
||||
totalSizeLimitStr := request.FormValue("totalSizeLimit")
|
||||
role := request.FormValue("role")
|
||||
|
||||
user := this.checkUser(request)
|
||||
sizeLimit := util.ExtractRequestInt64(request, "sizeLimit")
|
||||
totalSizeLimit := util.ExtractRequestInt64(request, "totalSizeLimit")
|
||||
|
||||
operator := this.checkUser(request)
|
||||
currentUser := this.userDao.CheckByUuid(uuid)
|
||||
|
||||
currentUser.AvatarUrl = avatarUrl
|
||||
|
||||
if user.Role == USER_ROLE_ADMINISTRATOR {
|
||||
//only admin can edit user's sizeLimit
|
||||
var sizeLimit int64 = 0
|
||||
if sizeLimitStr == "" {
|
||||
panic("user's limit size is required")
|
||||
} else {
|
||||
intSizeLimit, err := strconv.Atoi(sizeLimitStr)
|
||||
if err != nil {
|
||||
this.PanicError(err)
|
||||
}
|
||||
sizeLimit = int64(intSizeLimit)
|
||||
}
|
||||
currentUser.SizeLimit = sizeLimit
|
||||
|
||||
var totalSizeLimit int64 = 0
|
||||
if totalSizeLimitStr == "" {
|
||||
panic("user's total limit size is required")
|
||||
} else {
|
||||
intTotalSizeLimit, err := strconv.Atoi(totalSizeLimitStr)
|
||||
if err != nil {
|
||||
this.PanicError(err)
|
||||
}
|
||||
totalSizeLimit = int64(intTotalSizeLimit)
|
||||
}
|
||||
currentUser.TotalSizeLimit = totalSizeLimit
|
||||
if operator.Role == USER_ROLE_ADMINISTRATOR {
|
||||
//only admin can edit user's role and sizeLimit
|
||||
|
||||
if role == USER_ROLE_USER || role == USER_ROLE_ADMINISTRATOR {
|
||||
currentUser.Role = role
|
||||
}
|
||||
|
||||
} else if user.Uuid != uuid {
|
||||
} else if operator.Uuid == uuid {
|
||||
//cannot edit sizeLimit, totalSizeLimit
|
||||
space := this.spaceDao.CheckByUuid(currentUser.SpaceUuid)
|
||||
if space.SizeLimit != sizeLimit {
|
||||
this.logger.Error(" %s try to modify sizeLimit from %d to %d.", operator.Uuid, space.SizeLimit, sizeLimit)
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
if space.TotalSizeLimit != totalSizeLimit {
|
||||
this.logger.Error(" %s try to modify TotalSizeLimit from %d to %d.", operator.Uuid, space.TotalSizeLimit, totalSizeLimit)
|
||||
panic(result.BadRequestI18n(request, i18n.PermissionDenied))
|
||||
}
|
||||
|
||||
} else {
|
||||
panic(result.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
//edit user's info
|
||||
currentUser = this.userDao.Save(currentUser)
|
||||
|
||||
//edit user's private space info.
|
||||
space := this.spaceService.Edit(request, operator, currentUser.SpaceUuid, sizeLimit, totalSizeLimit)
|
||||
|
||||
//remove cache user.
|
||||
this.userService.RemoveCacheUserByUuid(currentUser.Uuid)
|
||||
|
||||
currentUser.Space = space
|
||||
|
||||
return this.Success(currentUser)
|
||||
}
|
||||
|
||||
@@ -307,6 +289,10 @@ func (this *UserController) Detail(writer http.ResponseWriter, request *http.Req
|
||||
|
||||
user := this.userDao.CheckByUuid(uuid)
|
||||
|
||||
//append the space info.
|
||||
space := this.spaceDao.FindByUuid(user.SpaceUuid)
|
||||
user.Space = space
|
||||
|
||||
return this.Success(user)
|
||||
|
||||
}
|
||||
@@ -391,9 +377,32 @@ func (this *UserController) Page(writer http.ResponseWriter, request *http.Reque
|
||||
|
||||
pager := this.userDao.Page(page, pageSize, username, status, sortArray)
|
||||
|
||||
//append the space info. FIXME: user better way to get Space.
|
||||
for _, u := range pager.Data.([]*User) {
|
||||
space := this.spaceDao.FindByUuid(u.SpaceUuid)
|
||||
u.Space = space
|
||||
}
|
||||
|
||||
return this.Success(pager)
|
||||
}
|
||||
|
||||
func (this *UserController) Search(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
keyword := request.FormValue("keyword")
|
||||
|
||||
pager := this.userDao.Page(0, 10, keyword, "", nil)
|
||||
|
||||
var resultList []*User = make([]*User, 0)
|
||||
for _, u := range pager.Data.([]*User) {
|
||||
resultList = append(resultList, &User{
|
||||
Uuid: u.Uuid,
|
||||
Username: u.Username,
|
||||
})
|
||||
}
|
||||
|
||||
return this.Success(resultList)
|
||||
}
|
||||
|
||||
func (this *UserController) ToggleStatus(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
@@ -439,13 +448,14 @@ func (this *UserController) Transfiguration(writer http.ResponseWriter, request
|
||||
return this.Success(session.Uuid)
|
||||
}
|
||||
|
||||
//scan user's physics files. create index into EyeblueTank
|
||||
// scan user's physics files. create index into EyeblueTank
|
||||
func (this *UserController) Scan(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
uuid := request.FormValue("uuid")
|
||||
currentUser := this.userDao.CheckByUuid(uuid)
|
||||
this.matterService.DeleteByPhysics(request, currentUser)
|
||||
this.matterService.ScanPhysics(request, currentUser)
|
||||
space := this.spaceDao.CheckByUuid(currentUser.SpaceUuid)
|
||||
this.matterService.DeleteByPhysics(request, currentUser, space)
|
||||
this.matterService.ScanPhysics(request, currentUser, space)
|
||||
|
||||
return this.Success("OK")
|
||||
}
|
||||
@@ -494,7 +504,7 @@ func (this *UserController) ChangePassword(writer http.ResponseWriter, request *
|
||||
return this.Success(user)
|
||||
}
|
||||
|
||||
//admin reset password.
|
||||
// admin reset password.
|
||||
func (this *UserController) ResetPassword(writer http.ResponseWriter, request *http.Request) *result.WebResult {
|
||||
|
||||
userUuid := request.FormValue("userUuid")
|
||||
|
||||
@@ -11,10 +11,17 @@ import (
|
||||
|
||||
type UserDao struct {
|
||||
BaseDao
|
||||
spaceDao *SpaceDao
|
||||
}
|
||||
|
||||
func (this *UserDao) Init() {
|
||||
this.BaseDao.Init()
|
||||
|
||||
b := core.CONTEXT.GetBean(this.spaceDao)
|
||||
if b, ok := b.(*SpaceDao); ok {
|
||||
this.spaceDao = b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *UserDao) Create(user *User) *User {
|
||||
@@ -36,7 +43,7 @@ func (this *UserDao) Create(user *User) *User {
|
||||
return user
|
||||
}
|
||||
|
||||
//find by uuid. if not found return nil.
|
||||
// find by uuid. if not found return nil.
|
||||
func (this *UserDao) FindByUuid(uuid string) *User {
|
||||
var entity = &User{}
|
||||
db := core.CONTEXT.GetDB().Where("uuid = ?", uuid).First(entity)
|
||||
@@ -50,7 +57,7 @@ func (this *UserDao) FindByUuid(uuid string) *User {
|
||||
return entity
|
||||
}
|
||||
|
||||
//find by uuid. if not found panic NotFound error
|
||||
// find by uuid. if not found panic NotFound error
|
||||
func (this *UserDao) CheckByUuid(uuid string) *User {
|
||||
entity := this.FindByUuid(uuid)
|
||||
if entity == nil {
|
||||
@@ -73,6 +80,20 @@ func (this *UserDao) FindByUsername(username string) *User {
|
||||
return user
|
||||
}
|
||||
|
||||
func (this *UserDao) FindAnAdmin() *User {
|
||||
|
||||
var user = &User{}
|
||||
db := core.CONTEXT.GetDB().Where(&User{Role: USER_ROLE_ADMINISTRATOR}).First(user)
|
||||
if db.Error != nil {
|
||||
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
|
||||
return nil
|
||||
} else {
|
||||
panic(db.Error)
|
||||
}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (this *UserDao) Page(page int, pageSize int, username string, status string, sortArray []builder.OrderPair) *Pager {
|
||||
|
||||
count, users := this.PlainPage(page, pageSize, username, status, sortArray)
|
||||
@@ -111,8 +132,8 @@ func (this *UserDao) PlainPage(page int, pageSize int, username string, status s
|
||||
return int(count), users
|
||||
}
|
||||
|
||||
//handle user page by page.
|
||||
func (this *UserDao) PageHandle(username string, status string, fun func(user *User)) {
|
||||
// handle user page by page.
|
||||
func (this *UserDao) PageHandle(username string, status string, fun func(user *User, space *Space)) {
|
||||
|
||||
//delete share and bridges.
|
||||
pageSize := 1000
|
||||
@@ -129,7 +150,8 @@ func (this *UserDao) PageHandle(username string, status string, fun func(user *U
|
||||
for page = 0; page < totalPages; page++ {
|
||||
_, users := this.PlainPage(0, pageSize, username, status, sortArray)
|
||||
for _, u := range users {
|
||||
fun(u)
|
||||
space := this.spaceDao.CheckByUuid(u.SpaceUuid)
|
||||
fun(u, space)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +176,7 @@ func (this *UserDao) Save(user *User) *User {
|
||||
return user
|
||||
}
|
||||
|
||||
//find all 2.0 users.
|
||||
// find all 2.0 users.
|
||||
func (this *UserDao) FindUsers20() []*User {
|
||||
var users []*User
|
||||
var wp = &builder.WherePair{}
|
||||
@@ -179,7 +201,7 @@ func (this *UserDao) Delete(user *User) {
|
||||
this.PanicError(db.Error)
|
||||
}
|
||||
|
||||
//System cleanup.
|
||||
// System cleanup.
|
||||
func (this *UserDao) Cleanup() {
|
||||
this.logger.Info("[UserDao] clean up. Delete all User")
|
||||
db := core.CONTEXT.GetDB().Where("uuid is not null and role != ?", USER_ROLE_ADMINISTRATOR).Delete(User{})
|
||||
|
||||
@@ -27,18 +27,18 @@ const (
|
||||
)
|
||||
|
||||
type User 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'"`
|
||||
Role string `json:"role" gorm:"type:varchar(45)"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null;unique"`
|
||||
Password string `json:"-" gorm:"type:varchar(255)"`
|
||||
AvatarUrl string `json:"avatarUrl" gorm:"type:varchar(255)"`
|
||||
LastIp string `json:"lastIp" gorm:"type:varchar(128)"`
|
||||
LastTime time.Time `json:"lastTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
SizeLimit int64 `json:"sizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
TotalSizeLimit int64 `json:"totalSizeLimit" gorm:"type:bigint(20) not null;default:-1"`
|
||||
TotalSize int64 `json:"totalSize" gorm:"type:bigint(20) not null;default:0"`
|
||||
Status string `json:"status" gorm:"type:varchar(45)"`
|
||||
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'"`
|
||||
Role string `json:"role" gorm:"type:varchar(45)"`
|
||||
Username string `json:"username" gorm:"type:varchar(45) not null;unique"`
|
||||
Password string `json:"-" gorm:"type:varchar(255)"`
|
||||
AvatarUrl string `json:"avatarUrl" gorm:"type:varchar(255)"`
|
||||
LastIp string `json:"lastIp" gorm:"type:varchar(128)"`
|
||||
LastTime time.Time `json:"lastTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
|
||||
|
||||
SpaceUuid string `json:"spaceUuid" gorm:"type:char(36);unique"`
|
||||
Status string `json:"status" gorm:"type:varchar(45)"`
|
||||
Space *Space `json:"space" gorm:"-"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/eyebluecn/tank/code/core"
|
||||
"github.com/eyebluecn/tank/code/tool/cache"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
@@ -8,21 +11,26 @@ import (
|
||||
"github.com/eyebluecn/tank/code/tool/uuid"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
//@Service
|
||||
// @Service
|
||||
type UserService struct {
|
||||
BaseBean
|
||||
userDao *UserDao
|
||||
sessionDao *SessionDao
|
||||
|
||||
//file lock
|
||||
spaceService *SpaceService
|
||||
|
||||
//file lock (legacy, kept for compatibility)
|
||||
locker *cache.Table
|
||||
// matterLocker is used for atomic matter operations with retry support
|
||||
matterLocker sync.Map
|
||||
|
||||
matterDao *MatterDao
|
||||
matterService *MatterService
|
||||
imageCacheDao *ImageCacheDao
|
||||
spaceDao *SpaceDao
|
||||
spaceMemberDao *SpaceMemberDao
|
||||
shareDao *ShareDao
|
||||
shareService *ShareService
|
||||
downloadTokenDao *DownloadTokenDao
|
||||
@@ -43,6 +51,11 @@ func (this *UserService) Init() {
|
||||
this.sessionDao = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.spaceService)
|
||||
if b, ok := b.(*SpaceService); ok {
|
||||
this.spaceService = b
|
||||
}
|
||||
|
||||
b = core.CONTEXT.GetBean(this.matterDao)
|
||||
if b, ok := b.(*MatterDao); ok {
|
||||
this.matterDao = b
|
||||
@@ -58,6 +71,15 @@ func (this *UserService) Init() {
|
||||
this.imageCacheDao = b
|
||||
}
|
||||
|
||||
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.shareService)
|
||||
if b, ok := b.(*ShareService); ok {
|
||||
this.shareService = b
|
||||
@@ -82,36 +104,37 @@ func (this *UserService) Init() {
|
||||
this.locker = cache.NewTable()
|
||||
}
|
||||
|
||||
//lock a user's operation. If lock, user cannot operate file.
|
||||
// lock a user's operation with retry mechanism.
|
||||
// This allows concurrent operations to wait for each other instead of immediately failing.
|
||||
// Uses sync.Map for thread-safe atomic operations.
|
||||
func (this *UserService) MatterLock(userUuid string) {
|
||||
maxRetries := 30 // Maximum retry attempts (30 * 200ms = 6 seconds max wait)
|
||||
retryInterval := 200 // Retry interval in milliseconds
|
||||
|
||||
cacheItem, err := this.locker.Value(userUuid)
|
||||
if err != nil {
|
||||
this.logger.Error("error while get cache" + err.Error())
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
// Try to acquire lock atomically
|
||||
if _, loaded := this.matterLocker.LoadOrStore(userUuid, true); !loaded {
|
||||
// Successfully acquired the lock
|
||||
return
|
||||
}
|
||||
|
||||
// Lock is held by another operation, wait and retry
|
||||
if i < maxRetries-1 {
|
||||
time.Sleep(time.Duration(retryInterval) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
if cacheItem != nil && cacheItem.Data() != nil {
|
||||
panic(result.BadRequest("file is being operating, retry later"))
|
||||
}
|
||||
|
||||
duration := 12 * time.Hour
|
||||
this.locker.Add(userUuid, duration, true)
|
||||
// After all retries, still cannot acquire lock
|
||||
panic(result.BadRequest("file is being operating, retry later"))
|
||||
}
|
||||
|
||||
//unlock
|
||||
// unlock
|
||||
func (this *UserService) MatterUnlock(userUuid string) {
|
||||
|
||||
exist := this.locker.Exists(userUuid)
|
||||
if exist {
|
||||
_, err := this.locker.Delete(userUuid)
|
||||
this.PanicError(err)
|
||||
} else {
|
||||
this.logger.Error("unlock error. %s has no matter lock ", userUuid)
|
||||
}
|
||||
this.matterLocker.Delete(userUuid)
|
||||
}
|
||||
|
||||
//load session to SessionCache. This method will be invoked in every request.
|
||||
//authorize by 1. cookie 2. username and password in request form. 3. Basic Auth
|
||||
// load session to SessionCache. This method will be invoked in every request.
|
||||
// authorize by 1. cookie 2. username and password in request form. 3. Basic Auth
|
||||
func (this *UserService) PreHandle(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
sessionId := util.GetSessionUuidFromRequest(request, core.COOKIE_AUTH_KEY)
|
||||
@@ -182,7 +205,7 @@ func (this *UserService) PreHandle(writer http.ResponseWriter, request *http.Req
|
||||
|
||||
}
|
||||
|
||||
//find a cache user by its userUuid
|
||||
// find a cache user by its userUuid
|
||||
func (this *UserService) FindCacheUsersByUuid(userUuid string) []*User {
|
||||
|
||||
var users []*User
|
||||
@@ -204,7 +227,7 @@ func (this *UserService) FindCacheUsersByUuid(userUuid string) []*User {
|
||||
return users
|
||||
}
|
||||
|
||||
//remove cache user by its userUuid
|
||||
// remove cache user by its userUuid
|
||||
func (this *UserService) RemoveCacheUserByUuid(userUuid string) {
|
||||
|
||||
var sessionId interface{}
|
||||
@@ -233,7 +256,32 @@ func (this *UserService) RemoveCacheUserByUuid(userUuid string) {
|
||||
}
|
||||
}
|
||||
|
||||
//delete user
|
||||
// create user
|
||||
func (this *UserService) CreateUser(request *http.Request, username string, sizeLimit int64, totalSizeLimit int64, password string, role string) *User {
|
||||
|
||||
user := &User{
|
||||
Username: username,
|
||||
Password: util.GetBcrypt(password),
|
||||
Role: role,
|
||||
Status: USER_STATUS_OK,
|
||||
}
|
||||
|
||||
user = this.userDao.Create(user)
|
||||
|
||||
//create space.
|
||||
space := this.spaceService.CreateSpace(request, username, user, sizeLimit, totalSizeLimit, SPACE_TYPE_PRIVATE)
|
||||
|
||||
//update user's space.
|
||||
user.SpaceUuid = space.Uuid
|
||||
this.userDao.Save(user)
|
||||
|
||||
user.Space = space
|
||||
|
||||
return user
|
||||
|
||||
}
|
||||
|
||||
// delete user
|
||||
func (this *UserService) DeleteUser(request *http.Request, currentUser *User) {
|
||||
|
||||
//delete from cache
|
||||
@@ -268,6 +316,15 @@ func (this *UserService) DeleteUser(request *http.Request, currentUser *User) {
|
||||
this.logger.Info("delete matters")
|
||||
this.matterDao.DeleteByUserUuid(currentUser.Uuid)
|
||||
|
||||
//delete space members
|
||||
space := this.spaceDao.CheckByUuid(currentUser.SpaceUuid)
|
||||
this.logger.Info("delete space members")
|
||||
this.spaceMemberDao.DeleteBySpaceUuid(space.Uuid)
|
||||
|
||||
//delete spaces
|
||||
this.logger.Info("delete spaces")
|
||||
this.spaceDao.DeleteByUserUuid(currentUser.Uuid)
|
||||
|
||||
//delete this user
|
||||
this.logger.Info("delete this user.")
|
||||
this.userDao.Delete(currentUser)
|
||||
|
||||
@@ -69,7 +69,7 @@ func (this *TankContext) Cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
//can serve as http server.
|
||||
// can serve as http server.
|
||||
func (this *TankContext) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
this.Router.ServeHTTP(writer, request)
|
||||
}
|
||||
@@ -80,10 +80,10 @@ func (this *TankContext) OpenDb() {
|
||||
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
|
||||
SlowThreshold: time.Second, // slow SQL 1s
|
||||
LogLevel: logger.Warn, // log level. open when debug.
|
||||
IgnoreRecordNotFoundError: true, // ignore ErrRecordNotFound
|
||||
Colorful: false, // colorful print
|
||||
},
|
||||
)
|
||||
|
||||
@@ -204,9 +204,25 @@ func (this *TankContext) registerBeans() {
|
||||
this.registerBean(new(rest.ShareDao))
|
||||
this.registerBean(new(rest.ShareService))
|
||||
|
||||
//space
|
||||
this.registerBean(new(rest.SpaceController))
|
||||
this.registerBean(new(rest.SpaceDao))
|
||||
this.registerBean(new(rest.SpaceService))
|
||||
|
||||
//space member
|
||||
this.registerBean(new(rest.SpaceMemberController))
|
||||
this.registerBean(new(rest.SpaceMemberDao))
|
||||
this.registerBean(new(rest.SpaceMemberService))
|
||||
|
||||
//uploadToken
|
||||
this.registerBean(new(rest.UploadTokenDao))
|
||||
|
||||
//chunk upload
|
||||
this.registerBean(new(rest.ChunkController))
|
||||
this.registerBean(new(rest.ChunkService))
|
||||
this.registerBean(new(rest.UploadSessionDao))
|
||||
this.registerBean(new(rest.UploadChunkDao))
|
||||
|
||||
//task
|
||||
this.registerBean(new(rest.TaskService))
|
||||
|
||||
@@ -241,7 +257,7 @@ func (this *TankContext) initBeans() {
|
||||
}
|
||||
}
|
||||
|
||||
//if application installed. invoke this method.
|
||||
// if application installed. invoke this method.
|
||||
func (this *TankContext) InstallOk() {
|
||||
|
||||
if core.CONFIG.Installed() {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func TestHello(t *testing.T) {
|
||||
|
||||
split := strings.Split("good", "/")
|
||||
split := strings.Split("", "")
|
||||
fmt.Printf("%v", split)
|
||||
|
||||
var i int
|
||||
|
||||
@@ -3,8 +3,6 @@ package download
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -14,6 +12,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eyebluecn/tank/code/tool/result"
|
||||
"github.com/eyebluecn/tank/code/tool/util"
|
||||
)
|
||||
|
||||
// HttpRange specifies the byte range to be sent to the client.
|
||||
@@ -313,6 +314,8 @@ func DownloadFile(
|
||||
sendSize = ra.length
|
||||
code = http.StatusPartialContent
|
||||
writer.Header().Set("Content-Range", ra.contentRange(size))
|
||||
// 把文件放进去
|
||||
io.CopyN(writer, diskFile, int64(ra.length))
|
||||
case len(ranges) > 1:
|
||||
sendSize = RangesMIMESize(ranges, ctype, size)
|
||||
code = http.StatusPartialContent
|
||||
|
||||
@@ -27,12 +27,14 @@ var (
|
||||
UsernameNotExist = &Item{English: `username "%s" not exists`, Chinese: `用户名"%s"不存在`}
|
||||
UsernameIsNotAdmin = &Item{English: `username "%s" is not admin user`, Chinese: `用户名"%s"不是管理员账号`}
|
||||
UsernameError = &Item{English: `username can only be letters, numbers or _`, Chinese: `用户名必填,且只能包含中文,字母,数字和'_'`}
|
||||
UserRoleError = &Item{English: `user role is not correct`, Chinese: `用户角色设置错误`}
|
||||
UserRegisterNotAllowd = &Item{English: `admin has banned register`, Chinese: `管理员已禁用自主注册`}
|
||||
UserPasswordLengthError = &Item{English: `password at least 6 chars`, Chinese: `密码长度至少为6位`}
|
||||
UserOldPasswordError = &Item{English: `old password error`, Chinese: `旧密码不正确`}
|
||||
UserDisabled = &Item{English: `user has been disabled`, Chinese: `用户已经被禁用了`}
|
||||
MatterDestinationMustDirectory = &Item{English: `destination must be directory'`, Chinese: `目标对象只能是文件夹。`}
|
||||
MatterExist = &Item{English: `"%s" already exists, invalid operation`, Chinese: `"%s" 已经存在了,操作无效`}
|
||||
MatterRecycleBinExist = &Item{English: `"%s" already exists in recycle bin, invalid operation`, Chinese: `"%s" 已经存在于回收站,请彻底删除后再操作`}
|
||||
MatterDepthExceedLimit = &Item{English: `directory's depth exceed the limit %d > %d`, Chinese: `文件加层数超过限制 %d > %d `}
|
||||
MatterNameLengthExceedLimit = &Item{English: `filename's length exceed the limit %d > %d`, Chinese: `文件名称长度超过限制 %d > %d `}
|
||||
MatterSelectNumExceedLimit = &Item{English: `selected files' num exceed the limit %d > %d`, Chinese: `选择的文件数量超出限制了 %d > %d `}
|
||||
@@ -46,6 +48,11 @@ var (
|
||||
ShareCodeRequired = &Item{English: `share code required`, Chinese: `提取码必填`}
|
||||
ShareCodeError = &Item{English: `share code error`, Chinese: `提取码错误`}
|
||||
CronValidateError = &Item{English: `cron error. five fields needed. eg: 1 * * * *`, Chinese: `Cron表达式错误,必须为5位。例如:1 * * * *`}
|
||||
SpaceNameError = &Item{English: `space's name can only be letters, numbers or _`, Chinese: `空间名称必填,且只能包含中文,字母,数字和'_'`}
|
||||
SpaceNameExist = &Item{English: `space's name "%s" exists`, Chinese: `空间名称"%s"已被占用,请使用其他名字`}
|
||||
SpaceExclusive = &Item{English: `user can only own ONE space`, Chinese: `一个用户只能拥有一个私有空间`}
|
||||
SpaceMemberExist = &Item{English: `space member %s exists`, Chinese: `用户 %s 已经是空间的成员`}
|
||||
PermissionDenied = &Item{English: `permission denied.`, Chinese: `没有操作权限`}
|
||||
)
|
||||
|
||||
func (this *Item) Message(request *http.Request) string {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
//whether windows develop environment
|
||||
// whether windows develop environment
|
||||
func EnvWinDevelopment() bool {
|
||||
|
||||
ex, err := os.Executable()
|
||||
@@ -18,7 +18,7 @@ func EnvWinDevelopment() bool {
|
||||
systemUser, err := user.Current()
|
||||
if systemUser != nil {
|
||||
|
||||
return strings.HasPrefix(ex, systemUser.HomeDir+"\\AppData\\Local\\Temp")
|
||||
return strings.HasPrefix(ex, systemUser.HomeDir+"\\AppData\\Local")
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func EnvWinDevelopment() bool {
|
||||
|
||||
}
|
||||
|
||||
//whether mac develop environment
|
||||
// whether mac develop environment
|
||||
func EnvMacDevelopment() bool {
|
||||
|
||||
ex, err := os.Executable()
|
||||
@@ -38,7 +38,7 @@ func EnvMacDevelopment() bool {
|
||||
|
||||
}
|
||||
|
||||
//whether develop environment (whether run in IDE)
|
||||
// whether develop environment (whether run in IDE)
|
||||
func EnvDevelopment() bool {
|
||||
|
||||
return EnvWinDevelopment() || EnvMacDevelopment()
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestString(request *http.Request, key string) string {
|
||||
str := request.FormValue(key)
|
||||
if str == "" {
|
||||
panic(fmt.Sprintf("%s is required", key))
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestStringWithErrorHint(request *http.Request, key string, errorHint string) string {
|
||||
str := request.FormValue(key)
|
||||
if str == "" {
|
||||
panic(errorHint)
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
// split by comma , param is required. when missing, panic error.
|
||||
func ExtractRequestArray(request *http.Request, key string) []string {
|
||||
str := request.FormValue(key)
|
||||
if str == "" {
|
||||
panic(fmt.Sprintf("%s is required", key))
|
||||
} else {
|
||||
arr := strings.Split(str, ",")
|
||||
if len(arr) == 0 {
|
||||
panic(fmt.Sprintf("%s cannot be empty", key))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
}
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestInt64(request *http.Request, key string) int64 {
|
||||
keyStr := request.FormValue(key)
|
||||
|
||||
if keyStr == "" {
|
||||
panic(fmt.Sprintf("%s is required", key))
|
||||
} else {
|
||||
intVal, err := strconv.ParseInt(keyStr, 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return intVal
|
||||
}
|
||||
}
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestInt64WithErrorHint(request *http.Request, key string, errorHint string) int64 {
|
||||
keyStr := request.FormValue(key)
|
||||
|
||||
var num int64 = 0
|
||||
if keyStr == "" {
|
||||
panic(errorHint)
|
||||
} else {
|
||||
intVal, err := strconv.Atoi(keyStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
num = int64(intVal)
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
// param is optional. when missing, use default.
|
||||
func ExtractRequestOptionalInt64(request *http.Request, key string, defaultValue int64) int64 {
|
||||
str := request.FormValue(key)
|
||||
if str == "" {
|
||||
return defaultValue
|
||||
} else {
|
||||
intVal, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return intVal
|
||||
}
|
||||
}
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestInt(request *http.Request, key string) int {
|
||||
keyStr := request.FormValue(key)
|
||||
|
||||
if keyStr == "" {
|
||||
panic(fmt.Sprintf("%s is required", key))
|
||||
} else {
|
||||
intVal, err := strconv.Atoi(keyStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return intVal
|
||||
}
|
||||
}
|
||||
|
||||
// param is optional. when missing, use default.
|
||||
func ExtractRequestOptionalInt(request *http.Request, key string, defaultValue int) int {
|
||||
str := request.FormValue(key)
|
||||
if str == "" {
|
||||
return defaultValue
|
||||
} else {
|
||||
intVal, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return intVal
|
||||
}
|
||||
}
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestOptionalString(request *http.Request, key string, defaultValue string) string {
|
||||
str := request.FormValue(key)
|
||||
if str == "" {
|
||||
return defaultValue
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestBool(request *http.Request, key string) bool {
|
||||
str := request.FormValue(key)
|
||||
if str == "true" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// param is required. when missing, panic error.
|
||||
func ExtractRequestTime(request *http.Request, key string) time.Time {
|
||||
str := request.FormValue(key)
|
||||
return ConvertDateTimeStringToTime(str)
|
||||
}
|
||||
|
||||
// param is optional. when missing, use default.
|
||||
func ExtractRequestOptionalBool(request *http.Request, key string, defaultValue bool) bool {
|
||||
str := request.FormValue(key)
|
||||
if str == "" {
|
||||
return defaultValue
|
||||
} else {
|
||||
return str == "true"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,36 @@
|
||||
module github.com/eyebluecn/tank
|
||||
|
||||
go 1.14
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/glebarez/sqlite v1.4.0
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||
golang.org/x/text v0.3.3
|
||||
gorm.io/driver/mysql v1.3.2
|
||||
gorm.io/gorm v1.23.2
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/text v0.16.0
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/term v0.22.0 // indirect
|
||||
modernc.org/libc v1.55.4 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.31.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,202 +1,101 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/glebarez/go-sqlite v1.14.8 h1:30RsIS/olgfOMr7SxiCaYhpq50BTteA/CUKaWVOOHYg=
|
||||
github.com/glebarez/go-sqlite v1.14.8/go.mod h1:gf9QVsKCYMcu+7nd+ZbDqvXnEXEb22qLcqRUQ9XEI34=
|
||||
github.com/glebarez/sqlite v1.4.0 h1:TvSCuOjSxIwY/bGyo2Yk5NvTy5nwUbirYM/eaq+yUfA=
|
||||
github.com/glebarez/sqlite v1.4.0/go.mod h1:xIxEsgI8j1uWS9RghOpxGje8MvygoFVBAByhlh/Nu64=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
|
||||
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
|
||||
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ=
|
||||
gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
|
||||
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
|
||||
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
|
||||
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
|
||||
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
|
||||
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
|
||||
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
|
||||
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
|
||||
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
|
||||
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
|
||||
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
|
||||
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
|
||||
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
|
||||
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
|
||||
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
|
||||
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
|
||||
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
|
||||
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
|
||||
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
|
||||
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
|
||||
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
|
||||
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
|
||||
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
|
||||
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
|
||||
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
|
||||
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
|
||||
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
|
||||
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
|
||||
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
|
||||
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
||||
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
||||
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
|
||||
modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
|
||||
modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
|
||||
modernc.org/ccgo/v3 v3.15.13/go.mod h1:QHtvdpeODlXjdK3tsbpyK+7U9JV4PQsrPGIbtmc0KfY=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/ccorpus v1.11.4/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
||||
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
||||
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
||||
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
|
||||
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
|
||||
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
|
||||
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
|
||||
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
|
||||
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
|
||||
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
|
||||
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
|
||||
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
|
||||
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
|
||||
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
|
||||
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
|
||||
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
|
||||
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
|
||||
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
|
||||
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
|
||||
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
|
||||
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
|
||||
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
|
||||
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
|
||||
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
|
||||
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
|
||||
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
|
||||
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
|
||||
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
|
||||
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
|
||||
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
|
||||
modernc.org/libc v1.14.5 h1:DAHvwGoVRDZs5iJXnX9RJrgXSsorupCWmJ2ac964Owk=
|
||||
modernc.org/libc v1.14.5/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.14.7 h1:A+6rGjtRQbt9SORXfV+hUyXOP3mDf7J5uz+EES/CNPE=
|
||||
modernc.org/sqlite v1.14.7/go.mod h1:yiCvMv3HblGmzENNIaNtFhfaNIwcla4u2JQEwJPzfEc=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.11.0/go.mod h1:zsTUpbQ+NxQEjOjCUlImDLPv1sG8Ww0qp66ZvyOxCgw=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/ccgo/v4 v4.20.4 h1:3pPOlMcblnu5CBU3w1BFtepwBnLezGjPYTH8xBeYZM8=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v2 v2.4.3 h1:Ik4ZcMbC7aY4ZDPUhzXVXi7GMub9QcXLTfXn3mWpNw8=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/libc v1.55.4 h1:iWzZ96v1Iut2Sm4lEp0yzFde20M9zpcI0wY3DFcb+8g=
|
||||
modernc.org/libc v1.55.4/go.mod h1:GPuVtbWvXUo590z/xfVIQcOqnugb1WovSfMJWMe0ZfA=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk=
|
||||
modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
|
||||
modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs=
|
||||
modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||