Compare commits

...

564 Commits

Author SHA1 Message Date
lealife
64a3177978 vendor 2017-07-27 14:45:11 +08:00
lealife
ed2ad19341 go vendor release 2017-06-22 17:12:39 +08:00
lealife
b140cd538f vendor 2017-06-22 13:18:16 +08:00
lealife
2654b684df upgrade revel to 0.16 2017-06-21 18:24:42 +08:00
life
d424395d85 Update .travis.yml 2017-06-21 18:09:53 +08:00
life
54810b3458 Merge pull request #628 from samtux/master
Translation to Spanish
2017-06-21 17:57:56 +08:00
Samuel Mesa
1cf66ad6c0 Translation to Spanish 2017-06-19 21:10:49 -05:00
lealife
6dc334ca51 Chrome 58 cannot select image 2017-05-19 11:50:50 +08:00
life
f9b4ead6be Merge pull request #590 from sundev126/patch-1
修复arm 32下/adminUser/index假死的bug
2017-04-16 21:36:13 +08:00
lealife
3bf6703929 blog paging configuration 2017-04-16 15:27:35 +08:00
Sunpy
ec657b9dcb 修复arm 32下/adminUser/index假死的bug
如果pageSize为0,则计算页数的时候会造成浮点数除0.0,由于浮点数除0得到的结果是`+Inf`,转换为整数后的结果是`2,147,483,647`,所以在渲染page.html模板时需要很长时间。
2017-04-13 18:00:42 +08:00
lealife
4ffd048b2a Form submission canceled because the form is not connected 2017-04-12 15:58:55 +08:00
lealife
6af19670da Upgrade revel from 0.13 to 0.14
https://github.com/revel/revel/releases/tag/v0.14.0
2017-04-08 18:55:42 +08:00
lealife
721e375d76 mind map 2017-03-07 13:38:04 +08:00
lealife
41f95cf0e5 2.4 released 2017-02-20 13:57:24 +08:00
lealife
ba411ef580 2.4 released 2017-02-20 13:51:07 +08:00
lealife
9dcf0ed53e delete unused 2017-02-20 13:48:26 +08:00
lealife
d5d853ffd3 Support note list view and sorter 2017-02-18 20:01:43 +08:00
lealife
ae29119664 set session.expires to 3h 2017-02-18 20:01:26 +08:00
lealife
ad644258f5 Support for leanote-chrome plugin 2017-02-18 20:01:14 +08:00
lealife
8c98f4da5c markdown remove smartypants; support table align 2017-02-09 11:39:30 +08:00
lealife
e2585bf695 2.3 released 2017-01-23 22:41:31 +08:00
lealife
3b9bc687c4 theme 2017-01-23 18:26:56 +08:00
lealife
2db182db1b 2.3 released 2017-01-23 17:43:41 +08:00
lealife
f3e1eb6c3f Add leaui mind map plugin 2017-01-23 17:43:13 +08:00
lealife
956e1ba2bb fix upload image error [api] 2017-01-23 17:42:38 +08:00
lealife
f294fa4124 fix cannot add deleted tags 2017-01-23 17:42:21 +08:00
lealife
cabf89f9b8 like bug 2017-01-23 17:42:04 +08:00
lealife
d183cd5a77 https://github.com/leanote/leanote/issues/513 2017-01-16 11:49:30 +08:00
lealife
000dfea92d 2.2.2 2017-01-07 15:55:31 +08:00
lealife
a53398ebb4 Fix export note image to pdf error 2017-01-07 15:53:22 +08:00
lealife
b23592229a update travis 2016-12-31 12:16:17 +08:00
lealife
584bde247e fix search deleted note 2016-12-31 12:10:27 +08:00
lealife
6774392807 2.2.1 2016-12-28 11:19:09 +08:00
lealife
964cf1a750 fix issue can't sync images on markdown notes 2016-12-28 10:27:08 +08:00
lealife
52128e6453 update qq groups 2016-12-26 15:45:48 +08:00
lealife
032470c3df v 2.2 2016-12-24 15:47:57 +08:00
lealife
a8dd578624 relative image url/fix conflict when upload attachment again 2016-12-24 15:16:23 +08:00
lealife
f49624d3eb Support send email with ssl 2016-10-29 16:38:13 +08:00
lealife
816af11db2 防止Unzip文件恶意攻击 2016-10-27 16:37:12 +08:00
lealife
eda03f0aa4 防止用"../../来获取其它文件" 2016-10-27 15:40:17 +08:00
lealife
6a06511405 防止用"../../来获取其它文件" 2016-10-27 15:09:10 +08:00
lealife
15d8ebdc0f New Chrome, Chinese input problem
https://github.com/leanote/leanote/issues/42
2016-09-24 10:44:10 +08:00
life
5dc929c4c0 Merge pull request #408 from zhang-yuan/master
Translation fixed
2016-08-26 09:55:50 +08:00
life
a4d6a99fee Merge pull request #409 from alexsourcerer/patch-9
Update album.conf  (fr-fr)
2016-08-26 09:54:59 +08:00
life
15d3b847cb Merge pull request #411 from alexsourcerer/patch-11
Update member.conf (fr-fr)
2016-08-26 09:54:49 +08:00
life
065ac99942 Merge pull request #412 from alexsourcerer/patch-12
Update msg.conf (fr-fr)
2016-08-26 09:54:33 +08:00
life
08ec359b93 Merge pull request #413 from alexsourcerer/patch-13
Update tinymce_editor.conf (fr-fr)
2016-08-26 09:54:03 +08:00
life
2f81c04f41 Merge pull request #378 from lucienevans/master
auto number in MathJax (just blog)
2016-08-26 09:52:23 +08:00
life
e8bff79425 Merge pull request #410 from alexsourcerer/patch-10
Update markdown.conf
2016-08-26 09:49:45 +08:00
alexsourcerer
4ca8e8cd83 Update tinymce_editor.conf
translation created & completed!
2016-08-23 12:41:32 +02:00
alexsourcerer
c0cc08a3c2 Update msg.conf
translation updated!
2016-08-23 12:04:53 +02:00
alexsourcerer
d32a6dd277 Update member.conf
updated translation to french
2016-08-23 11:57:22 +02:00
alexsourcerer
8bacc15309 Update markdown.conf
translated to french
2016-08-23 11:46:26 +02:00
alexsourcerer
8a3f24b9c6 Update album.conf
translated to french
2016-08-23 11:37:16 +02:00
haha
2a7d1fe7b5 Add translation 2016-08-23 14:09:53 +08:00
haha
9a05ffcd8f 翻译问题 2016-08-23 12:11:41 +08:00
lealife
aa95e680f9 travis use go 1.4 2016-08-23 11:06:50 +08:00
life
874ab69c34 Merge pull request #406 from Xunius/patch-2
add vim and emacs modes to features
2016-08-23 10:33:13 +08:00
Xunius
d43f5fd7ab add vim and emacs modes to features 2016-08-22 17:44:39 +08:00
life
6a048f0d9a Merge pull request #403 from Xunius/patch-1
rephrased English n Chinese, fixed links to docs.
2016-08-22 17:23:21 +08:00
Xunius
cb3681f651 rephrased English n Chinese, fixed links to docs.
Fixed grammar errors in English and polished the wordings.
Changed order at one place to make the Doc part appear before develop.
Added links to installation tutorials
2016-08-19 19:12:30 +08:00
life
dfd7159401 Merge pull request #361 from gaoyifan/patch-1
fix spelling mistake in README.md
2016-07-22 10:36:10 +08:00
lucien
929ec975da auto number in MathJax 2016-06-21 23:35:49 +08:00
life
e826dc7b93 Merge pull request #374 from mhoffmann75/german_transl
add complete german translation
2016-06-10 17:12:21 +08:00
Martin Hoffmann
629a49e54b add complete german translation
- modify app/views/home/header.html to know de-de messages folder
- create messages/de-de folder with all german language files (album, blog, ...)
- create public/js/i18n/blog.de-de.js and ms.de-de.js files
- add public/tinymce/langs/de-de.js from tinymce project

Thanks to Marc Manthey (macbroadcast) who is the original author of the german translation for the leanote 1.x project (https://github.com/macbroadcast/leanote). I took his work as starting point for my complete german translation on leanote 2.0.
2016-06-10 09:46:31 +02:00
lealife
6ebedf94f0 app.conf 2016-05-25 17:33:38 +08:00
lealife
a07bb25128 remove unused codes and add site'url configuration on admin 2016-05-25 16:05:47 +08:00
lealife
a007b89ebb v2.0 2016-05-25 14:46:40 +08:00
lealife
cc4b003503 markdown vim cursor fixed 2016-05-25 09:51:53 +08:00
lealife
091922175a box leanote logo 2016-05-22 16:12:51 +08:00
lealife
371c93912d build 2016-05-22 10:36:36 +08:00
lealife
27221acda7 new logo 2016-05-22 10:34:40 +08:00
lealife
5db724e80b new logo 2016-05-21 23:05:50 +08:00
Yifan Gao
14265d9de4 fix spelling mistake in README.md 2016-05-18 10:26:24 +08:00
lealife
5c9e95ed60 build 2016-04-16 14:53:30 +08:00
lealife
2fc97ed42d markdown editor prettify add linenums 2016-04-16 14:51:54 +08:00
lealife
45cca30cec lea++ -> Explore 2016-04-16 12:09:48 +08:00
lealife
31167b6aaf recount tags 2016-04-16 11:58:39 +08:00
lealife
139906b9d6 recount tags when public note 2016-04-16 11:31:27 +08:00
lealife
a9d49369e2 upgrade markdown mathjax 2016-04-12 22:02:36 +08:00
life
303e0da5c0 Update README.md 2016-04-09 10:12:46 +08:00
life
ff5523bfaa Merge pull request #340 from eric2100/patch-15
Update blog.conf
2016-04-08 17:07:20 +08:00
Tsunghan,Tsai
063dd0140d Update blog.conf
部分口語修改
2016-04-08 12:31:41 +08:00
life
c52d388d5d Merge pull request #337 from eric2100/patch-14
Update msg.conf
2016-04-07 17:31:04 +08:00
eric2100
18bcdb48af Update msg.conf 2016-04-07 12:29:03 +08:00
life
db8f132507 Merge pull request #333 from eric2100/patch-9
Update blog.conf
2016-04-05 10:14:31 +08:00
life
439d5212b0 Merge pull request #334 from eric2100/patch-10
Update markdown.conf
2016-04-05 10:13:40 +08:00
life
88e2320027 Merge pull request #335 from eric2100/patch-11
Update member.conf
2016-04-05 10:13:09 +08:00
life
659a4e590a Merge pull request #336 from eric2100/patch-12
Update tinymce_editor.conf
2016-04-05 10:12:19 +08:00
eric2100
ab7f2386e0 Update tinymce_editor.conf 2016-04-05 00:24:21 +08:00
eric2100
ec82ff18f2 Update member.conf 2016-04-05 00:16:00 +08:00
eric2100
f3f00626e6 Update markdown.conf 2016-04-05 00:03:27 +08:00
eric2100
80fbb56426 Update blog.conf 2016-04-05 00:01:46 +08:00
life
a5c952882f Merge pull request #332 from eric2100/patch-8
Update album.conf
2016-04-03 21:30:30 +08:00
eric2100
973b452a7e Update album.conf 2016-04-02 21:12:30 +08:00
lealife
25bcd21ea9 overwrite revel.Message 2016-03-19 23:00:41 +08:00
lealife
09de5e1e83 Gulp js i18n generator 2016-03-19 15:46:41 +08:00
lealife
15b0ef21ff restructure messages 2016-03-19 15:37:36 +08:00
lealife
9cf9ded855 remove config langs. Gulp will get all langs by messages/ 2016-03-19 15:17:15 +08:00
lealife
7af433ebaf build 2016-03-19 14:36:47 +08:00
lealife
67b608b324 set langs to config and modify gulp build to support langs 2016-03-19 14:30:07 +08:00
lealife
2aa3f5a654 support 中文繁体 2016-03-19 14:05:09 +08:00
lealife
f5538218bd remove baidu statistics Code
https://github.com/leanote/leanote/issues/318
2016-03-04 22:23:53 +08:00
lealife
0e3bdd177c md resize when toggle 2016-02-27 15:30:01 +08:00
lealife
12520cc09d mathjax 2.6.1 2016-02-27 14:20:31 +08:00
life
cb67892124 Merge pull request #309 from alexsourcerer/patch-8
Update msg.fr
2016-02-18 14:55:17 +08:00
alexsourcerer
45c09c9a26 Update msg.fr
I updated the msg.fr file (note that the website isn't it french at all anymore for french users)
2016-02-03 23:10:50 +01:00
lealife
20951ea2dd build release 2015-12-25 23:59:57 +08:00
lealife
530f2368da v1.4.2 released 2015-12-25 23:45:38 +08:00
lealife
e685a99628 member css for multi lang 2015-12-25 23:45:19 +08:00
lealife
bfc5acc3ee 1.4.2 released 2015-12-25 11:50:34 +08:00
lealife
551e3d965e markdown todo-list style 2015-12-25 11:50:34 +08:00
lealife
7c479b5595 fix ace editor hide the last line part section text 2015-12-25 11:50:34 +08:00
lealife
b135f9717f fix gen desc 2015-12-25 11:50:34 +08:00
lealife
08ae37cae0 markdown add todo-list feature 2015-12-25 11:50:34 +08:00
Yingbo CUI
315a7bbe62 Update README.md 2015-12-24 16:47:31 +08:00
life
e173b18ddc Merge pull request #285 from eurafa/master
Portuguese (Brazil) language added
2015-12-17 10:29:10 +08:00
Rafael Andrade de Oliveira
dcc25df679 Chinese translations 2015-12-13 09:09:02 -02:00
Rafael Andrade de Oliveira
c561dec14c Portuguese (Brazil) language added 2015-12-13 09:03:50 -02:00
lealife
43e8f2e701 markdown editor inited 2015-12-06 15:48:05 +08:00
lealife
dd9952c453 fix export pdf error 2015-12-04 17:50:14 +08:00
lealife
19517dd634 fix export pdf error 2015-12-04 17:37:57 +08:00
lealife
a5b006aae7 添加全局 toggle modify with readonly; 发布1.4.1 2015-12-02 20:56:03 +08:00
lealife
90bd07cdcc * markdown v2, 修复笔记内容覆盖 2015-12-02 20:55:34 +08:00
lealife
92576e9239 arm run script 2015-11-29 13:05:26 +08:00
lealife
c58e6874c2 paste image 名称是untitled 2015-11-28 21:47:50 +08:00
lealife
cfebf00cf1 i18n js 2015-11-28 21:32:21 +08:00
lealife
19be0990da 注销后不再保存笔记 2015-11-28 21:31:56 +08:00
lealife
3f27e4b0d4 Failed to generate name for field. Make sure the field name is valid #201 2015-11-28 21:26:29 +08:00
lealife
4710fb6686 i18n 2015-11-28 18:57:58 +08:00
lealife
28324c0178 release v1.4 2015-11-28 18:00:45 +08:00
lealife
f270ade8d7 1.4 2015-11-28 17:58:42 +08:00
lealife
3b3e665a9f paste image 2015-11-28 17:58:38 +08:00
lealife
bfa85e2d34 Merge branch 'feature-v1.4' 2015-11-28 16:45:59 +08:00
lealife
fcf270a521 reinit undo when toggle file 2015-11-28 16:44:59 +08:00
lealife
eba113c73c dev tinymce 2015-11-28 16:40:50 +08:00
lealife
41f63156ea tinymce 不打包 2015-11-28 16:32:29 +08:00
lealife
f54fe397c3 member view 2015-11-28 16:12:27 +08:00
lealife
6f157289d3 update member css 2015-11-28 16:09:33 +08:00
lealife
65792d020c move tinymce theme to /public/css/tinymce 2015-11-28 16:07:17 +08:00
lealife
926882dce6 build js 2015-11-28 15:56:25 +08:00
lealife
f834f469fc update gulp build for i18n 2015-11-28 15:55:58 +08:00
lealife
06d42c36f0 i18n 2015-11-28 15:46:56 +08:00
lealife
17d513a9de fix note-dev 2015-11-28 15:42:39 +08:00
lealife
8cc00c1f66 format date config; for update note's date 2015-11-28 15:38:43 +08:00
lealife
cb433d9392 theme; split by files 2015-11-28 15:36:12 +08:00
lealife
86ca1efd11 tinymce theme 2015-11-28 15:35:26 +08:00
lealife
263f978c54 import theme 2015-11-28 15:33:49 +08:00
lealife
c8af7eda5d hide note and editor when search 2015-11-28 15:33:42 +08:00
lealife
3724811802 set md column width 2015-11-28 15:33:30 +08:00
lealife
10ff48267f clear undo 2015-11-28 15:33:13 +08:00
lealife
cd0913aec0 paste for markdown v2 2015-11-28 15:33:06 +08:00
lealife
f8d4c2ef20 markdown editor v2 2015-11-28 15:32:56 +08:00
lealife
f16b5e816e Leanote upperCase 2015-11-28 15:23:33 +08:00
lealife
9a9968760a note i18n 2015-11-28 15:19:50 +08:00
lealife
b1f36dfbf1 member i18n 2015-11-28 15:17:36 +08:00
lealife
bc0e09f222 file upload path 统一化 GetRandomFilePath 2015-11-28 14:44:07 +08:00
lealife
0dab71e72b delete unused 2015-11-28 14:35:46 +08:00
lealife
10f23c19ab group i18n, pwd 重置密码优化 2015-11-28 14:28:51 +08:00
lealife
bba57ea302 configservice 2015-11-28 14:25:45 +08:00
lealife
f052117989 删除笔记时删除历史 2015-11-28 14:22:10 +08:00
lealife
df4ec53647 api fixContent优化; exportPDF优化 2015-11-28 14:21:23 +08:00
lealife
a7eaf6114a urlTitle 优化, 减少生成次数, 避免使用id作为urlTitle 2015-11-28 14:16:10 +08:00
lealife
b25bf0e16c util 时间判断 2015-11-28 13:49:00 +08:00
lealife
d13447e6d0 controller 支持传递时间 2015-11-28 13:47:59 +08:00
lealife
6a1e84da0c noteService支持传时间添加/更新笔记 2015-11-28 13:43:37 +08:00
lealife
51aeb7d46e build support linux-arm 2015-11-23 10:39:01 +08:00
lealife
2dbd025768 album js build 2015-11-22 23:26:59 +08:00
lealife
d52d80d529 plugins compress 2015-11-22 23:18:07 +08:00
lealife
2000bc585b markdown editor v2 paste images 2015-11-22 23:01:30 +08:00
lealife
8301a1e515 js 优化
* 标题按tab切换到内容
* 左侧最小化用css控制
* resize markdown editor v2
* Ace剪切导致行数减少
* 删除不要的代码
2015-11-22 22:58:38 +08:00
lealife
bee65eb36d markdown editor v2 editor mode i18n 2015-11-22 22:46:27 +08:00
lealife
78199583b0 mv dist -> md 2015-11-22 22:46:00 +08:00
lealife
28d5678349 remove unused files 2015-11-22 22:41:07 +08:00
lealife
e97c468f6e gulp markdown v2 builder 2015-11-22 22:39:16 +08:00
lealife
5d18554542 tinymce build 2015-11-22 22:39:04 +08:00
lealife
3ff0a9e09c markdown editor v2 libs 2015-11-22 22:36:53 +08:00
lealife
6a753682b7 markdown editor v2 2015-11-22 22:33:07 +08:00
lealife
ce491e62c3 insert image i18n 2015-11-22 22:28:51 +08:00
lealife
e4b847b731 undo md 2015-11-22 22:21:59 +08:00
lealife
a12b285a35 tinymce leanote code plugin i18n 2015-11-22 22:21:38 +08:00
lealife
7f0ada8016 update readme 2015-11-18 23:45:20 +08:00
lealife
bac5ab4d79 update readme 2015-11-18 23:43:19 +08:00
life
b362406819 Merge pull request #261 from alexsourcerer/patch-7
Update msg.fr
2015-11-18 13:13:54 +08:00
alexsourcerer
9e2edbb987 Update msg.fr 2015-11-16 21:56:12 +01:00
lealife
7809d00787 fmt all go file 2015-11-13 17:58:41 +08:00
lealife
cba69444a8 v1.3.1 released 2015-10-30 14:44:57 +08:00
lealife
5e02816c09 v1.3.1 release 2015-10-30 14:41:01 +08:00
lealife
20b085aeae Markdown编辑器中文回车有时不换行 #249 2015-10-30 14:12:32 +08:00
lealife
0f5e683a4f Markdown 笔记无法分辨 hyperlink #240 2015-10-30 14:09:35 +08:00
lealife
e134ca7244 个个中心美化 2015-10-30 14:07:42 +08:00
lealife
6818574605 删除leaui不要文件; file/outputImage 变成 api/file/getImage 2015-10-30 14:05:51 +08:00
lealife
6eea03f81e 自动保存优化, 时间缩短成10s
https://github.com/leanote/leanote/issues/243
2015-10-30 14:02:28 +08:00
lealife
1242834b7f 增加修改与只读的快捷键 ctrl/cmd + e
https://github.com/leanote/leanote/issues/241
2015-10-30 13:57:28 +08:00
lealife
1844513f08 mac下cmd+shift+c 不能调出toggle code
https://github.com/leanote/leanote/issues/248
2015-10-30 13:52:50 +08:00
lealife
b0be7a14d2 ctrl+v 复制图片, 有时不显示, 但上传了
https://github.com/leanote/leanote/issues/227
2015-10-30 13:48:54 +08:00
lealife
7e17c68dba update readme 2015-10-27 23:44:53 +08:00
lealife
6259f15ffa update readme 2015-10-27 23:42:19 +08:00
lealife
f65d027a0d update readme 2015-10-27 23:25:17 +08:00
lealife
e9e2ca50ee update readme 2015-10-27 23:19:56 +08:00
lealife
a64ef8517e update readme 2015-10-27 23:17:15 +08:00
lealife
c9bda60554 v1.3 release 2015-10-27 23:14:02 +08:00
lealife
c3478520c0 batch release 2015-10-27 23:08:00 +08:00
lealife
8346ffe915 batch release
https://github.com/leanote/leanote/issues/238
2015-10-27 23:07:49 +08:00
lealife
f49b046601 Merge branch 'feature-batch' 2015-10-27 23:06:51 +08:00
lealife
263c03291e batch ok 2015-10-27 23:04:39 +08:00
lealife
29244c247e lit indent 2015-10-20 09:28:03 +08:00
lealife
320de8637f v1.2 update initial db data 2015-10-18 20:34:44 +08:00
lealife
33bc6bf84b v1.2 update initial data 2015-10-18 20:34:31 +08:00
lealife
0cd2c42b68 v1.2 release 2015-10-18 00:16:54 +08:00
lealife
1716acf9e0 release v1.2 for export pdf 2015-10-17 23:46:09 +08:00
lealife
45477119d4 Merge branch 'feature-pdf' 2015-10-17 23:23:36 +08:00
lealife
716fbeae0c update wkhtmltopdf link 2015-10-17 23:22:40 +08:00
lealife
103891e0c0 wkhtmltopdf configuration 2015-10-17 19:36:49 +08:00
lealife
638b5b51e0 export pdf ok 2015-10-17 17:15:33 +08:00
lealife
90fea722aa 笔记内拖动图片, release 2015-10-17 15:55:14 +08:00
lealife
90ce35327f 笔记内拖动图片, 拖动上传图片
https://github.com/leanote/leanote/issues/231
2015-10-17 15:53:35 +08:00
lealife
60e3b9446a 空白笔记前有一个空格 2015-10-17 15:51:21 +08:00
lealife
16a726223c 富文本编辑器不支持 多层级列表
https://github.com/leanote/leanote/issues/234
2015-10-17 15:42:40 +08:00
lealife
dfe45f39bb i18n 2015-10-14 19:03:41 +08:00
lealife
413be4a6d0 markdown editor, image manager, [ok] 2015-10-14 18:53:29 +08:00
lealife
6a35f98f89 image manager optimization, i18n 2015-10-14 18:47:01 +08:00
lealife
ad295d97c8 markdown editor i18n 2015-10-14 18:38:08 +08:00
lealife
cf8171c9cd member upload avatar 2015-10-12 21:18:01 +08:00
lealife
c616a4e9a6 download attach 2015-10-12 21:08:29 +08:00
lealife
04d641a5ad download attach 2015-10-12 21:03:51 +08:00
lealife
ec68570a38 attach list 2015-10-12 20:55:08 +08:00
lealife
407b44382c avatar upload 2015-10-12 19:44:43 +08:00
lealife
71a2bcc7f0 v1.1 Proudly powered by <a href="https://leanote.com">Leanote</a> 2015-10-12 12:15:53 +08:00
lealife
a2dd09725c v1.1 update theme <div class="footer-leanote">Proudly powered by <a href="https://leanote.com">Leanote</a></div> 2015-10-12 12:09:44 +08:00
lealife
f757e00f03 Merge branch 'sync-from-leanote.com' 2015-10-10 18:10:41 +08:00
lealife
b7652bb321 opt, fix 1.1 2015-10-10 18:10:26 +08:00
lealife
e6018d32ec tinymce 复制纯文本bug 2015-10-10 17:41:18 +08:00
lealife
cb7e272bbe leanote v1.1 release 2015-10-10 17:23:27 +08:00
lealife
a4d9d8e0d3 leanote v1.1 build 2015-10-10 16:55:36 +08:00
lealife
c844c8b188 复制外链图片, 复制到leanote 2015-10-10 16:18:26 +08:00
lealife
320263eefa 笔记信息, 历史记录, 提示, 附件优化
https://github.com/leanote/leanote/issues/224
2015-10-10 16:10:54 +08:00
lealife
274875c6c4 note dev 优化 2015-10-10 15:24:26 +08:00
lealife
1ea35324c1 内容的IsBlog没有note IsBlog一致, 导致前端判断有问题 2015-10-10 15:20:34 +08:00
lealife
3e20ad3cdd CPU性能过高优化, 字符串多次连接导致
https://github.com/leanote/leanote/issues/223
2015-10-10 15:06:48 +08:00
lealife
711e6b3d17 注册邮箱支持多个"_", 如 a__a@a.com 2015-10-10 15:02:55 +08:00
lealife
0438bbb414 email 不让修改! 2015-10-10 14:53:50 +08:00
lealife
70ee362cc8 db connection lost #222 2015-10-10 14:49:02 +08:00
lealife
0479f1a433 attach bug 2015-10-10 14:42:49 +08:00
lealife
7e01cb8227 防止blog urlTitle 无限循环 2015-10-10 14:40:52 +08:00
lealife
ae0eb3b918 get all attachs 权限未验证 2015-10-10 14:38:18 +08:00
lealife
097d2709e2 tag标题不能为空 2015-10-10 14:33:40 +08:00
lealife
f1e56272ef 修改email只能小写 2015-10-10 14:32:30 +08:00
lealife
db4cfbf605 已删除的笔记还显示 #226 2015-10-10 14:28:53 +08:00
lealife
da7d31fa00 files, upload下文件过多
https://github.com/leanote/leanote/issues/225
2015-10-10 14:12:22 +08:00
lealife
62bc74d3c6 update msg 2015-10-10 13:51:42 +08:00
lealife
4467689ec3 add debug mode 2015-10-10 13:46:58 +08:00
life
69a874d90d Merge pull request #214 from nosqldb/master
compress images
2015-10-07 00:48:30 +08:00
lealife
b259a00c94 update default conf 2015-10-07 00:29:29 +08:00
lealife
a0088ead9e update default conf 2015-10-07 00:28:35 +08:00
duoyun
228fd80abd compress images 2015-09-21 21:39:28 +08:00
lealife
8b3a1a646a update travis 2015-09-18 13:48:06 +08:00
lealife
c4954b94b8 rmove testrunner 2015-09-18 13:44:25 +08:00
lealife
2b56ebd620 update travis 2015-09-18 13:28:51 +08:00
lealife
a78d95ddad update travis 2015-09-18 13:10:23 +08:00
lealife
938362154a update travis 2015-09-18 13:06:04 +08:00
lealife
8bff2b7000 delete session 2015-09-18 12:55:57 +08:00
lealife
c56f228646 delete memcache 2015-09-18 12:21:32 +08:00
lealife
98f0313e3e update travis 2015-09-18 12:17:07 +08:00
lealife
bde2e891ef update travis 2015-09-18 11:38:55 +08:00
lealife
7acdaede0d update travis 2015-09-18 11:35:17 +08:00
lealife
45d1b4bee3 add cmd to gen routes.go, main.go 2015-09-18 11:30:38 +08:00
lealife
4d0a170f01 update travis 2015-09-18 11:23:00 +08:00
lealife
5e37e5a37d add adminUsername on app.conf 2015-09-17 18:52:10 +08:00
lealife
a60da3d592 1.0 版在网页端删除笔记本出错
https://github.com/leanote/leanote/issues/207
2015-09-17 17:14:57 +08:00
lealife
9a88fab84e fix demo 2015-09-17 12:30:57 +08:00
lealife
4981d6aad4 move tests to app/tests 2015-09-16 11:14:37 +08:00
life
7193ac6833 Merge pull request #206 from duoyun/patch-1
use english  double quote
2015-09-14 21:57:36 +08:00
Cyrus.Chu
3d1a32764d use english double quote 2015-09-14 09:03:02 +08:00
lealife
2b8df3ed76 update travis 2015-09-13 21:53:01 +08:00
lealife
e94b004a5a update travis 2015-09-13 21:43:47 +08:00
lealife
6cc6f26fa1 travis 2015-09-13 21:40:50 +08:00
lealife
120cdd53c3 update travis 2015-09-13 21:34:30 +08:00
lealife
435aac4a9c https://github.com/leanote/leanote/issues/205 2015-09-13 21:23:58 +08:00
lealife
f234cf285d update travis 2015-09-11 18:41:47 +08:00
lealife
54953b69c6 add travis status 2015-09-11 18:40:08 +08:00
lealife
60878b7a65 ad travis 2015-09-11 18:36:51 +08:00
lealife
dada88d8a2 add travis 2015-09-11 18:34:38 +08:00
lealife
7167705409 add db/auth test 2015-09-11 18:31:37 +08:00
life
4217abca6e Merge pull request #203 from duoyun/master
remove unused func LoginGetUserInfo
2015-09-11 17:40:16 +08:00
duoyun
49a97f2285 remove unused func LoginGetUserInfo 2015-09-10 20:36:22 +08:00
lealife
4f2d7b8cd0 get mongodb configuration from env 2015-09-09 10:10:24 +08:00
lealife
afbda7bfb2 reset conf 2015-09-07 22:20:53 +08:00
lealife
c568756d16 使用Crypto加密, 找回密码, 修改密码修复 2015-09-07 15:39:46 +08:00
life
5794f76b0a Merge pull request #195 from duoyun/master
change md5 to bcrypt to strengthen  password hash and keep md5
2015-09-07 15:02:25 +08:00
life
74a768c0fb Merge pull request #196 from bencevans/patch-1
English Translation Updates
2015-09-07 14:58:13 +08:00
Ben Evans
4136060825 English Translation UnTitled -> Untitled 2015-09-07 00:16:50 +01:00
Ben Evans
e249a953cd English Translation Update 2015-09-07 00:04:56 +01:00
Ben Evans
eddc2e6356 English Translation Updates 2015-09-07 00:04:18 +01:00
duoyun
1604474d6e mv crypto to lea 2015-09-07 00:29:02 +08:00
duoyun
bbaf71481c use bcrypt and keep Md5 2015-09-06 23:16:56 +08:00
lealife
952117818c delete unused 2015-09-06 10:36:10 +08:00
lealife
a31432f1bf update package.json 2015-09-05 23:56:12 +08:00
lealife
b0afe25730 gulp build 2015-09-05 23:48:41 +08:00
lealife
024e0e9bae ace editor 最后没有元素, 或者元素不是p, 则在最后插入之 2015-09-05 23:46:02 +08:00
lealife
dbabc3a5a8 use gulp to build leanote front-end 2015-09-05 23:42:10 +08:00
lealife
a30aec8254 界面优化 2015-09-05 23:30:31 +08:00
lealife
7440218974 界面优化 2015-09-05 23:30:24 +08:00
life
b3c771a87f Merge pull request #182 from ZhuangER/yu-huang
fix a nav bar's bug
2015-09-05 22:47:34 +08:00
ZhuangEr
8f8451cdff fix iss183 2015-08-31 14:15:52 -04:00
yu huang
6d7fe83469 move script from footer.js
have fixed bug of multiple active of email and send to users.
also make URL like "http://localhost:9000/admin/t?p=0&t=email/sendToUsers" work.
2015-08-29 19:19:11 -04:00
yu huang
05a5280162 move inline script code to admin.js 2015-08-29 19:15:08 -04:00
yu huang
8dd55eb949 Update footer.html 2015-08-29 19:13:54 -04:00
life
5838cc218f Merge pull request #180 from ZhuangER/yu-huang
Fix a nav bar's bug
2015-08-29 13:23:09 +08:00
yu huang
2e52c957e0 Update nav.html 2015-08-28 20:10:05 -04:00
yu huang
9486c58d78 Update nav.html 2015-08-28 20:04:57 -04:00
lealife
6e7c1e0d41 add chat room 2015-08-27 10:25:05 +08:00
lealife
6a0a60a644 update readme 2015-08-21 23:52:28 +08:00
lealife
94f9c2c3cb update readme 2015-08-20 23:42:12 +08:00
lealife
37e7ed8d14 update readme 2015-08-20 23:37:55 +08:00
lealife
4227c80ec7 Merge branch 'master' of github.com:leanote/leanote
merge it
2015-08-20 23:34:46 +08:00
lealife
6ec6ef698b add qq group2 2015-08-20 23:30:46 +08:00
life
0543dd015e Merge pull request #159 from alexsourcerer/patch-6
Update msg.fr
2015-07-13 18:22:28 +08:00
alexsourcerer
b88f895acf Update msg.fr
I made some translation mistakes I did not see before.
2015-06-16 12:52:56 +02:00
lealife
fc517700cc v1.0 release.sh 2015-06-15 19:47:22 +08:00
lealife
b5e0cd31dd v1.0 reset app.conf 2015-06-15 19:13:59 +08:00
lealife
ab4b8b77a3 v1.0 use revel 0.12 2015-06-15 19:13:00 +08:00
lealife
04c339896b v1.0 preview 2015-06-15 18:55:33 +08:00
lealife
e37056737d v1.0 preview 2015-06-15 18:55:07 +08:00
lealife
89f8bb934c v1.0 initial db data 2015-06-15 18:53:10 +08:00
lealife
b564989214 v1.0 initial db data 2015-06-15 18:53:02 +08:00
lealife
03aa8edd58 v1.0 french lang 2015-06-15 18:24:28 +08:00
lealife
803fc90dd9 v1.0 remove unused code 2015-06-15 18:07:52 +08:00
lealife
6987a38820 v1.0
只读模式
group, 分享
评论更多问题
博客标签总是存在一个
2015-06-15 18:01:48 +08:00
lealife
7e458bb433 分享给项目组无法输出已设置项目组 #143 2015-05-21 20:49:58 +08:00
lealife
b391375008 font-awesome.css找不到 #140 2015-05-14 19:23:56 +08:00
lealife
3f89e1cf83 update readme 2015-05-10 15:10:56 +08:00
lealife
9f5dc8a0e2 update readme 2015-05-10 15:07:51 +08:00
lealife
12ea17e89f 编译之前修改的 2015-05-07 00:13:04 +08:00
lealife
1fd6610fa3 #133 修改默认主题, 更新markdown解析器 2015-05-07 00:02:54 +08:00
lealife
1ec28d80c8 添加markdown解析器 for blog 博客与note的markdown解析器不一致 #133 2015-05-06 23:54:44 +08:00
lealife
fcbc23e964 Ace编辑器出现XXXXXXX #132 2015-05-06 23:54:10 +08:00
lealife
6fba2a6416 test for commit 2015-05-06 23:39:56 +08:00
life
0e5edb16f6 #131 2015-05-06 23:32:45 +08:00
life
5726df1a81 update read 2015-05-03 12:32:16 +08:00
life
f3a0a786c5 release 2015-04-20 20:51:03 +08:00
life
fa08b3393a restore dbname 2015-04-20 19:52:13 +08:00
life
5db148be9f release for windows 2015-04-20 19:51:20 +08:00
life
d03cce2761 initial db data fixed 2015-04-08 14:01:34 +08:00
life
3a0bdd803d initial data fixed 2015-04-08 14:01:24 +08:00
life
d1b6dddfd0 readme 2015-04-07 20:34:09 +08:00
life
2676c5a85d Readme 2015-04-07 20:32:50 +08:00
life
bde731d84c Merge pull request #108 from goodbest/patch-3
fix restore/backup bug: mongo -port to --port
2015-04-03 00:32:49 +08:00
life
46bc714b45 Merge pull request #109 from goodbest/patch-4
When admin username changes, this link is dead
2015-04-02 16:20:18 +08:00
goodbest
7269226422 When admin username changes, this link is dead 2015-04-02 16:05:08 +08:00
life
c390d12dd2 update controller 2015-04-02 13:58:02 +08:00
goodbest
68eaac9622 mongo3 dont't have --directoryperdb option;
Just append dumpPath by the end of the command is capable for both mongo2 and 3
2015-04-02 10:43:46 +08:00
goodbest
a089292461 fix restore/backup bug: mongo -port to --port 2015-04-02 10:18:59 +08:00
life
b59eba4ca4 Merge pull request #104 from Refactoring/master
实现组内人员可分享内容至这个组
2015-04-02 09:43:12 +08:00
life
8e52074346 Merge pull request #105 from goodbest/patch-2
add missing key for notebook
2015-04-02 09:38:08 +08:00
goodbest
4fb7e8dbaa add missing key for notebook 2015-04-01 12:54:37 +08:00
Refactoring
8ed260976a 删除无用的.svn文件夹 2015-04-01 11:10:40 +08:00
Refactoring
d8dac67913 Merge remote-tracking branch 'masterfork/master'
Conflicts:
	app/lea/Util.go
	public/js/common-min.js
	public/js/common.js
2015-04-01 08:55:08 +08:00
life
ade77bba58 Update app.conf 2015-03-31 18:34:45 +08:00
life
849118f9b8 update api doc 2015-03-31 18:27:53 +08:00
life
f0b45deaec Merge branch 'dev' 2015-03-31 18:17:57 +08:00
life
5ebfe301f8 fix note 2015-03-31 18:16:47 +08:00
life
801263f50c Merge branch 'dev' 2015-03-31 18:13:21 +08:00
life
6bd6f70cbf merge dev-life 2015-03-31 18:08:21 +08:00
life
f38a86cb07 v1.0-beta.4 fix conflict 2015-03-31 18:00:49 +08:00
life
f755574576 Merge branch 'dev'
Conflicts:
	conf/app.conf
2015-03-31 17:59:16 +08:00
life
d568c286d5 initial db data 2015-03-31 17:57:10 +08:00
life
cbf5c7f941 initial db data 2015-03-31 17:56:58 +08:00
life
ce0e099bbd fix conflicts 2015-03-31 17:52:18 +08:00
life
fbf819f4d2 merge dev-life 2015-03-31 17:29:16 +08:00
life
bb65ef992a Note._toHtmlEntity move to common.js 2015-03-31 17:04:22 +08:00
life
ab0ee68f39 fix conflict 2015-03-31 16:56:35 +08:00
life
f959694b0f fix conflict 2015-03-31 16:56:26 +08:00
life
5fae5dd10e Merge branch 'dev-life' into dev
Conflicts:
	app/init.go
	app/service/UserService.go
	conf/app.conf
	conf/routes
	mongodb_backup/leanote_install_data/albums.metadata.json
	mongodb_backup/leanote_install_data/attachs.metadata.json
	mongodb_backup/leanote_install_data/blog_comments.metadata.json
	mongodb_backup/leanote_install_data/blog_likes.metadata.json
	mongodb_backup/leanote_install_data/blog_singles.metadata.json
	mongodb_backup/leanote_install_data/configs.bson
	mongodb_backup/leanote_install_data/configs.metadata.json
	mongodb_backup/leanote_install_data/email_logs.metadata.json
	mongodb_backup/leanote_install_data/files.metadata.json
	mongodb_backup/leanote_install_data/find_pwds.metadata.json
	mongodb_backup/leanote_install_data/group_users.metadata.json
	mongodb_backup/leanote_install_data/groups.metadata.json
	mongodb_backup/leanote_install_data/has_share_notes.metadata.json
	mongodb_backup/leanote_install_data/leanote.ShareNotes.metadata.json

mongodb_backup/leanote_install_data/leanote.has_share_notes.metadata.jso
n

mongodb_backup/leanote_install_data/note_content_histories.metadata.json
	mongodb_backup/leanote_install_data/note_contents.metadata.json
	mongodb_backup/leanote_install_data/note_images.metadata.json
	mongodb_backup/leanote_install_data/notebooks.bson
	mongodb_backup/leanote_install_data/notebooks.metadata.json
	mongodb_backup/leanote_install_data/notes.bson
	mongodb_backup/leanote_install_data/notes.metadata.json
	mongodb_backup/leanote_install_data/reports.metadata.json
	mongodb_backup/leanote_install_data/sessions.metadata.json
	mongodb_backup/leanote_install_data/share_notebooks.metadata.json
	mongodb_backup/leanote_install_data/share_notes.metadata.json
	mongodb_backup/leanote_install_data/suggestions.metadata.json
	mongodb_backup/leanote_install_data/system.indexes.bson
	mongodb_backup/leanote_install_data/tag_count.metadata.json
	mongodb_backup/leanote_install_data/tags.metadata.json
	mongodb_backup/leanote_install_data/themes.metadata.json
	mongodb_backup/leanote_install_data/tokens.metadata.json
	mongodb_backup/leanote_install_data/user_blogs.metadata.json
	mongodb_backup/leanote_install_data/users.bson
	mongodb_backup/leanote_install_data/users.metadata.json
	public/blog/themes/default/theme.json
	public/blog/themes/elegant/theme.json
	public/blog/themes/nav_fixed/theme.json
	public/images/logo/leanote_icon_blue.jpg
	public/js/app/note-min.js
	public/js/app/note.js
	public/js/app/notebook-min.js
	public/js/app/notebook.js
	public/js/app/tag-min.js
	public/js/app/tag.js
	public/tinymce/plugins/paste/plugin.dev.js
	public/tinymce/plugins/spellchecker/plugin.min.js
	public/tinymce/tinymce.dev.js
	public/tinymce/tinymce.jquery.dev.js
2015-03-31 16:18:22 +08:00
life
9aacba4c31 conf 2015-03-31 14:39:03 +08:00
life
e47dc0dc18 beta.4 initial data 2015-03-31 14:34:54 +08:00
life
0b3d69e73c beta.4 initial data 2015-03-31 14:34:08 +08:00
life
decf580ed3 API, Tag 2015-03-31 14:27:26 +08:00
Refactoring
e186cf984c #96 实现组内的任何一个人都可以分享内容到这个组中.
存在不足:共享内容以分享者进行归类组织.  更好的内容组织方式:共享给组的以组归类组织,共享给个人的以分享者个人组织
2015-03-30 20:19:48 +08:00
life
3b1d9e6a73 Merge pull request #95 from Deathis/dev
修复移动端用户头像不是绝对地址导致某些情况地址错误的问题
2015-03-29 00:14:18 +08:00
Deathis
cb218086f3 修复移动端用户头像不是绝对地址导致某些情况地址错误的问题
将用户头像设置成绝对地址
2015-03-28 23:42:53 +08:00
Refactoring
0f7ebb6c53 fix bug: Email validation missing contains "-"
example: name1-name2@site.com, name@site1-site2.com
2015-03-27 14:56:37 +08:00
leanote
257a141519 Update README_zh.md 2015-03-05 17:27:15 +08:00
leanote
12bc62e990 Update README.md 2015-03-05 17:26:24 +08:00
leanote
fbeecdf062 Update README.md 2015-03-05 17:25:20 +08:00
leanote
1e0fd166db Update README.md
Leanote V1.0.3-beta bin url
2015-03-05 17:24:29 +08:00
dds_feng
2484abe7cf Merge branch 'dev-xqin' into dev 2015-01-24 17:31:18 +08:00
dds_feng
cbaa52e57b staticUrl -> siteUrl 2015-01-24 17:28:28 +08:00
yfqin
407e4c3ac4 笔记的Tag中不允许出现换行符 2015-01-20 14:08:52 +08:00
yfqin
8a61b64575 Merge branch 'dev-xqin' into dev 2015-01-20 13:13:58 +08:00
yfqin
419585ff7d _toHtmlEntity 对双引号进行转义
对其他地方中有可能产生XSS的地方进行修补
2015-01-20 11:31:17 +08:00
dds_feng
ffaaa8c11a 对笔记的Tag中由JS创建的DOM节点在进行内容设置时对HTML进行转义 2015-01-20 01:15:55 +08:00
dds_feng
3417e71d04 Merge branch 'dev-xqin' into dev 2015-01-20 00:07:19 +08:00
dds_feng
014a141808 修复当笔记的Title中有HTML代码时,会被页面解析的问题 2015-01-20 00:06:36 +08:00
life
9803249ab1 Update app.conf 2015-01-13 20:04:05 +08:00
life
ac99e20aa4 default theme clean & replace install data 2015-01-10 20:51:50 +08:00
life
d5dabecb81 blog theme redesign 2015-01-10 19:04:46 +08:00
life
df9bc37e1d blog theme redesign 2015-01-10 19:04:06 +08:00
life
4277caa571 initial data, add index to notes 2015-01-09 00:32:34 +08:00
life
1d18908cd0 add images 2015-01-09 00:23:33 +08:00
life
916ee52cdb Update conf 2015-01-09 00:13:20 +08:00
life
289e7b16d9 build 2015-01-08 23:33:14 +08:00
life
016ef5de2d delete app.conf-default, routes-default 2015-01-08 23:29:24 +08:00
life
52d0a7ed21 delete medium theme 2015-01-08 23:27:13 +08:00
life
ddf3236c4f Merge branch 'dev-life'
Conflicts:
	app/init.go
	public/js/common-min.js
	public/js/common.js
2015-01-08 23:14:27 +08:00
life
9515f1e58f 打包发布最新功能 2015-01-08 22:08:09 +08:00
life
c514d0bc1c Ace & Markdown全新编辑器 & others
邀请注册
共享后图片可见问题 fileService::getFile()
笔记历史记录问题
共享后得到被共享者列表问题 shareService
themeService 博客主题新建问题, 模板循环引用问题
markdown编辑器双屏大小不能保存问题
2015-01-08 21:15:56 +08:00
life
d24531dc78 update messages 2015-01-08 00:38:54 +08:00
life
2cfc89ca5f ace editor, markdown editor 2015-01-08 00:36:28 +08:00
dds_feng
b0d4005ad6 修复在note的界面下从菜单中点退出,浏览器报错,导致退出不了的问题 2014-12-29 21:34:23 +08:00
asktalk
c0cd433c3f fixed bug #5 2014-12-28 09:07:16 +08:00
asktalk
792c8cfd40 medium theme 2014-12-27 23:47:31 +08:00
asktalk
4f102ff883 Medium theme 2014-12-27 23:46:11 +08:00
asktalk
40973c4615 modify the product publicity picture 2014-12-27 23:32:32 +08:00
asktalk
58bf623d05 edit readme 2014-12-27 23:28:03 +08:00
asktalk
46e97abe91 edit readme 2014-12-27 23:25:32 +08:00
life
be01c9c3f7 刷新共享笔记时有问题 2014-12-09 23:43:14 +08:00
life
17718732cc 国际化, #21, #26, 2014-12-09 23:17:36 +08:00
life
e2e90f8618 Merge branch 'master' of github.com:leanote/leanote 2014-12-07 22:05:30 +08:00
life
25d5df6bfc update readme 2014-12-07 22:02:01 +08:00
asktalk
b781f8318c fixed bug #24 2014-12-07 00:31:51 +08:00
asktalk
0e07cbab8b updated README.md 2014-12-07 00:23:00 +08:00
asktalk
ed75704032 添加默认笔记本,默认笔记本无法删除。修改初始化数据添加默认笔记本数据。修改bug #23 2014-12-07 00:12:17 +08:00
life
c49593171c note 2014-12-02 23:20:10 +08:00
life
45d72051d0 Note 2014-12-02 23:19:43 +08:00
life
10b387faa6 compatible for iphone/ipad 2014-12-02 22:56:48 +08:00
life
30ebc95abf compatible for iPhone/ipad 2014-12-02 22:47:12 +08:00
life
9bc65bc099 compatible for iPhone/ipad; writing mode for ipad/iphone 2014-12-02 22:42:46 +08:00
life
39809dc4c6 update read me 2014-11-21 09:52:37 +08:00
life
8c4a203d96 sort cate beta2 2014-11-17 14:21:25 +08:00
life
034ad821ca beta2 preNextBlog fixed 2014-11-13 16:46:22 +08:00
life
c894b7b308 beta2 google code pretty js 2014-11-13 14:21:38 +08:00
life
c0979be9f3 markdown table theme 2014-11-13 13:02:05 +08:00
life
b45e9eacb0 beta2 preview controller 2014-11-13 12:35:48 +08:00
life
16d47418ad beta2 default theme url title 2014-11-13 10:31:11 +08:00
life
5d48d1853d use urlTitle for post url 2014-11-12 23:57:50 +08:00
life
f503da30b7 member blog list avatar 2014-11-12 23:12:10 +08:00
life
11e282de9a beta.2 released 2014-11-12 21:12:02 +08:00
life
8f86754bf2 group 2014-11-12 20:39:04 +08:00
life
e2f125b253 theme data 2014-11-12 20:35:30 +08:00
life
0e55b1d83d ace editor 2014-11-12 20:24:41 +08:00
life
52a3ac13b3 remove unused js 2014-11-12 20:24:24 +08:00
life
6f12e179eb leanote beta2 release 2014-11-12 20:08:07 +08:00
life
4669fbcbdd leaui_image form url css 2014-11-12 19:43:42 +08:00
life
51fedfa6dc default themes and fix notebook to blog bug 2014-11-12 19:36:31 +08:00
life
cd3c2f7b12 default / 2014-11-12 19:08:41 +08:00
life
a6d3fe9cb8 beta default routes 2014-11-12 19:00:09 +08:00
life
9d47b4eac9 beta2 default data 2014-11-12 18:57:43 +08:00
life
163215d547 beta2 theme 2014-11-12 18:54:42 +08:00
life
4d7a9c8089 Merge branch 'feature-blog-theme' 2014-11-12 17:38:55 +08:00
life
4e1f8f3d6e beta2 small fixed 2014-11-12 17:37:36 +08:00
life
320c78e925 beta2 default data 2014-11-12 17:35:08 +08:00
life
6a4d7d1056 beta2 ok 2014-11-12 17:32:22 +08:00
life
1f45666ec4 beta2 ok 2014-11-12 17:32:03 +08:00
life
d979a0c3e2 blog, tags, archives 2014-11-10 23:56:15 +08:00
life
6555384e5c siteurl, adminUsername config in configService 2014-11-10 16:26:04 +08:00
life
954c4e5e95 for setBlog 2014-11-09 22:26:38 +08:00
life
8ef91b7418 test 2014-11-09 22:14:59 +08:00
life
e5f7c66d1e test 2014-11-09 22:14:08 +08:00
life
346abfe91d upload file size limit [ok] 2014-11-09 18:00:23 +08:00
life
2a457d6027 reset password 2014-11-09 16:54:56 +08:00
life
274782b89e member center, blog redesign 2014-11-09 16:24:19 +08:00
life
5f186f4455 theme preview 2014-11-05 11:33:07 +08:00
life
f20565d54b just for rename 2014-10-28 14:48:22 +08:00
life
af9a820cbf just for test 2014-10-28 14:47:58 +08:00
life
33e2428b64 views 2014-10-28 14:40:37 +08:00
life
35c06771e7 delte unsless images 2014-10-26 11:30:29 +08:00
life
2f81a529fc useless js, css clear 2014-10-26 11:19:27 +08:00
life
5d559da2a6 Update ConfigService.go 2014-10-25 01:12:58 +08:00
life
fe910fd91d update readme 2014-10-24 10:01:47 +08:00
life
1c6c645c9f v1.0 readme 2014-10-23 11:52:51 +08:00
life
04838cc996 v1.0 readme 2014-10-23 11:49:25 +08:00
life
86ecc55f41 v1.0-beta release 2014-10-23 11:41:02 +08:00
life
e4323d0cb2 leanote v1.0 release 2014-10-23 10:52:48 +08:00
life
28fbdb9ee2 open register init data 2014-10-23 10:47:04 +08:00
life
b0b304d5dd admin username & jsonp fixed 2014-10-23 10:45:03 +08:00
life
536c5de56a v1.0 routes 2014-10-23 10:10:12 +08:00
life
1f51f6cc9b v1.0 fixed 2014-10-23 10:03:19 +08:00
life
0ea4843cac v1.0 2014-10-22 22:30:49 +08:00
life
ebcce0a247 v1.0 2014-10-22 22:24:46 +08:00
life
b43c68ec2d Merge branch 'develop-v1.0' 2014-10-22 19:19:40 +08:00
life
762f6b554d conf-default 2014-10-22 17:04:37 +08:00
life
593d2c2965 v1.0 beta init 2014-10-22 16:20:45 +08:00
life
8ae438272b change moto and delete lea 2014-10-15 17:31:27 +08:00
life
f99cca40c2 add qq group 2014-10-14 22:58:24 +08:00
life
3f1930723a lea pagination 2014-09-25 11:37:37 +08:00
life
9fbbde9849 lea++ style fixed 2014-09-24 23:13:37 +08:00
life
3bade30e1a lea++ 2014-09-24 23:01:57 +08:00
life
44c8f2a7e2 fix 2014-09-24 22:56:06 +08:00
life
8d820b069c blog css fixed image width 2014-09-24 22:43:33 +08:00
life
f16ba28a3b merge develop 2014-09-24 22:32:05 +08:00
life
9db1164fe0 merge develop 2014-09-24 22:31:53 +08:00
life
37563d1869 nav 2014-09-24 22:28:42 +08:00
life
87269cc939 Merge branch 'develop'
admin [init ok]
lea++ blog platform [ok]
2014-09-24 22:24:52 +08:00
life
99956cfd72 fix animation when toggle writing mod 2014-09-24 10:06:30 +08:00
life
cff6efde91 common page & animation 2014-09-23 18:56:04 +08:00
life
2221f146de fix TinyMCE Removes site base url 2014-09-22 22:45:57 +08:00
life
1fea36a7c1 fix 2014-09-22 22:43:33 +08:00
life
95af247cdc attach fix download url 2014-09-22 22:31:56 +08:00
life
5a2274328b attach fix download url 2014-09-22 22:26:50 +08:00
life
0e6f777402 update readme 2014-09-22 22:24:18 +08:00
life
84f5e9c969 slimscroll fixed js t() -> tt() 2014-09-22 20:21:58 +08:00
life
c216e3a1ea attach fixed 2014-09-22 20:09:25 +08:00
life
b4f0a08b9f attach fixed 2014-09-22 20:09:12 +08:00
life
02536f6de4 attach css fixed firefox 2014-09-22 20:04:23 +08:00
life
50b6af0446 attach css fixed firefox 2014-09-22 19:58:25 +08:00
life
416dd77717 init service 2014-09-22 19:15:28 +08:00
life
b89721a0f4 tagColor dropdown 2014-09-22 14:39:54 +08:00
life
b8ced2e1c3 donate 2014-09-22 14:06:46 +08:00
life
e240dfbafe dropdown triangle 2014-09-22 12:38:48 +08:00
life
b411302087 file size 2014-09-22 00:58:43 +08:00
life
e86bbb9b02 Merge branch 'develop-feature' 2014-09-22 00:43:50 +08:00
life
7bd5d66c55 #10, #14 2014-09-22 00:41:17 +08:00
life
6b194a63c6 Merge branch 'develop-feature' 2014-09-21 22:53:14 +08:00
life
5439c1b5fb #10 #14 2014-09-21 22:52:37 +08:00
life
ca9be9cd81 Merge branch 'develop-feature' 2014-09-21 22:20:29 +08:00
life
320c79e7a3 #10 #14 2014-09-21 22:20:00 +08:00
life
2ddbeb5b11 #10 #14 [ok]
add attachment feature,
1) upload, delete,
2) link attach into content (include tinymce & markdown)

markdown-editor.js add insertLink function to add link into markdown
content, usage:
MarkdownEditor.insertLink(link, title)

paste plugin edit for safety image
2014-09-21 22:09:54 +08:00
life
c556ab59b5 attachment feature #10 2014-09-21 22:05:04 +08:00
life
ab242b10f2 paste image when edit/add shared note 2014-09-21 00:29:46 +08:00
life
81c2254cfb copy shared note must copy images 2014-09-20 15:20:36 +08:00
life
9f89a3c717 #14 safety image 2014-09-19 17:29:24 +08:00
iiuazz
6f4ba8313c for safety image v1.0 #14 2014-09-19 17:18:53 +08:00
life
c142568a56 leanote's api init 2014-09-18 00:33:22 +08:00
life
65e84d30db new leanote router for leanote's api
/api/user/info => convert it to ApiUser.Info()
2014-09-18 00:28:23 +08:00
iiuazz
76a111c6b0 username lower 2014-09-15 19:37:24 +08:00
iiuazz
4d48ecad49 drop down animation 2014-09-15 16:13:27 +08:00
iiuazz
9bf3b4c2bc update readme 2014-09-15 14:03:06 +08:00
iiuazz
9bdce1a46c css firefox bug 2014-09-15 11:29:02 +08:00
iiuazz
b41101e073 release v1.0 2014-09-13 13:25:49 +08:00
iiuazz
cc9cb271c4 delete bin 2014-09-13 13:19:18 +08:00
iiuazz
b5661edd85 fix merge conflict 2014-09-13 13:16:05 +08:00
iiuazz
a60e49364b merge 2014-09-13 13:12:05 +08:00
iiuazz
cdd6aa035b Merge branch 'develop'
Conflicts:
	public/css/theme/default.css
	public/css/theme/simple.css
	public/css/theme/writting-overwrite.css
	public/css/theme/writting.css
2014-09-13 13:06:53 +08:00
iiuazz
522bbd9ed4 css 2014-09-13 13:02:04 +08:00
iiuazz
138dfa904c fix 2014-09-13 12:54:56 +08:00
leanote
94e3f543ab Update LICENSE 2014-09-13 10:52:13 +08:00
leanote
634ad35813 Update LICENSE 2014-09-13 10:44:01 +08:00
iiuazz
20a39d5128 fix 2014-09-13 00:41:28 +08:00
iiuazz
27dbd6552c contexmenu dynamic 2014-09-13 00:20:18 +08:00
iiuazz
d1f18b9476 v1.0-alpha release 2014-09-12 21:46:03 +08:00
iiuazz
8dd5239a8a default theme [ok] 2014-09-12 21:40:14 +08:00
iiuazz
e6fb6e3f09 all is ok, waitting to edit default theme 2014-09-12 20:42:40 +08:00
iiuazz
52010e4fc1 share notebook [ok] 2014-09-12 20:31:14 +08:00
iiuazz
adf59976ec email 2014-09-12 15:39:28 +08:00
iiuazz
f30430bc63 before sharenotebooks 2014-09-12 15:32:19 +08:00
iiuazz
a113b9b5e5 item setting [ok]
notebook icon [ok]
2014-09-12 11:50:24 +08:00
iiuazz
85fd63baa5 min left ok 2014-09-12 09:55:16 +08:00
iiuazz
4b723eb331 prepare to fix min 2014-09-11 22:46:26 +08:00
iiuazz
9b96f0fbd1 move, copy notebooks contextmenu ok 2014-09-11 21:49:15 +08:00
iiuazz
ca4eb3aef5 search notebook for list [ok] 2014-09-11 20:55:07 +08:00
iiuazz
883ea1da62 choose notebook to add note/markdown [ok] 2014-09-11 18:55:42 +08:00
iiuazz
dc2435a83d add notebook, add sub notebook, delete, rename [ok] 2014-09-11 14:31:25 +08:00
iiuazz
2bed5b31fa add ztree to drag and sort notebooks 2014-09-10 22:44:43 +08:00
iiuazz
bfcf8ec547 album 2014-09-09 19:34:59 +08:00
iiuazz
c30cb745f9 test 2014-09-09 19:31:43 +08:00
leanote
db2e78c5a5 Update .project 2014-09-09 18:51:44 +08:00
iiuazz
e4b003d063 update test 2014-09-09 18:50:33 +08:00
life
f3993519ea update ignore 2014-09-09 18:43:31 +08:00
life
02d0411d53 bbs 2014-09-05 16:16:51 +08:00
life
77f4b0f383 bbs 2014-09-05 13:56:28 +08:00
life
af9b5652ed leanote_icon_blue.png 2014-09-05 10:58:38 +08:00
life
fbad32e273 v0.4 released 2014-09-04 17:49:17 +08:00
3498 changed files with 524889 additions and 150272 deletions

9
.gitignore vendored
View File

@@ -2,17 +2,18 @@
pkg
bin2
bin/i18n
bin/leanote-linux
bin/leanote-mac
bin/leanote*
bin/release
bin/test.sh
bin/tmp
bin/test
bin/src
conf/app.conf
conf/routes
public/upload
app/routes/routes.go
app/tmp/main.go
.DS_Store
.settings
.project
public/config.codekit
files
/node_modules

36
.travis.yml Normal file
View File

@@ -0,0 +1,36 @@
language: go
go: 1.7
services:
- mongodb # 2.4.12
install:
- export PATH=$PATH:$HOME/gopath/bin
- go get -v github.com/leanote/leanote/app
- go get -v github.com/leanote/leanote/cmd
# - go get -u github.com/revel/cmd/revel
# - ls $GOPATH/src/github.com/revel/
# - go get github.com/revel/moudle/revel
# - go install github.com/revel/cmd/revel
- pwd
- ls
script:
- mongo --version
- mongorestore -h localhost -d leanote --directoryperdb ./mongodb_backup/leanote_install_data/
# gen tmp/main.go, routes/routes.go
- go run app/cmd/main.go
# build
- go build -o leanote github.com/leanote/leanote/app/tmp
# run with port 9000
- ./leanote -importPath=github.com/leanote/leanote -runMode=dev -port=9000 &
- sleep 10s;
# test
- curl http://localhost:9000
- curl http://localhost:9000/blog
- curl http://localhost:9000/login
- curl http://localhost:9000/demo
# - revel build github.com/leanote/leanote tmp
# OK

409
Gulpfile.js Normal file
View File

@@ -0,0 +1,409 @@
var gulp = require('gulp');
var clean = require('gulp-clean');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var minifyHtml = require("gulp-minify-html");
var concat = require('gulp-concat');
var replace = require('gulp-replace');
var inject = require('gulp-inject');
var gulpSequence = require('gulp-sequence');
var fs = require('fs');
var leanoteBase = './';
var base = leanoteBase + '/public'; // public base
var noteDev = leanoteBase + '/app/views/note/note-dev.html';
var noteProBase = leanoteBase + '/app/views/note';
var messagesPath = leanoteBase + 'messages';
// 合并Js, 这些js都是不怎么修改, 且是依赖
// 840kb, 非常耗时!!
gulp.task('concatDepJs', function() {
var jss = [
'js/jquery-1.9.0.min.js',
'js/jquery.ztree.all-3.5-min.js',
// 'tinymce/tinymce.full.min.js', // 使用打成的包, 加载速度快
// 'libs/ace/ace.js',
'js/jQuery-slimScroll-1.3.0/jquery.slimscroll-min.js',
'js/contextmenu/jquery.contextmenu-min.js',
'js/bootstrap-min.js',
'js/object_id.js',
];
for(var i in jss) {
jss[i] = base + '/' + jss[i];
}
return gulp
.src(jss)
// .pipe(uglify()) // 压缩
.pipe(concat('dep.min.js'))
.pipe(gulp.dest(base + '/js'));
});
// 合并app js 这些js会经常变化 90kb
gulp.task('concatAppJs', function() {
var jss = [
'js/common.js',
'js/app/note.js',
'js/app/page.js', // 写作模式下, page依赖note
'js/app/tag.js',
'js/app/notebook.js',
'js/app/share.js',
];
for(var i in jss) {
jss[i] = base + '/' + jss[i];
}
return gulp
.src(jss)
.pipe(uglify()) // 压缩
.pipe(concat('app.min.js'))
.pipe(gulp.dest(base + '/js'));
});
// plugins压缩
gulp.task('plugins', function() {
// gulp.src(base + '/js/plugins/libs/*.js')
// .pipe(uglify()) // 压缩
// // .pipe(concat('main.min.js'))
// .pipe(gulp.dest(base + '/js/plugins/libs-min'));
// 所有js合并成一个
var jss = [
'note_info',
'tips',
'history',
'attachment_upload',
'editor_drop_paste',
'main'
];
for(var i in jss) {
jss[i] = base + '/js/plugins/' + jss[i] + '.js';
}
jss.push(base + '/js/plugins/libs-min/fileupload.js');
return gulp.src(jss)
.pipe(uglify()) // 压缩
.pipe(concat('main.min.js'))
.pipe(gulp.dest(base + '/js/plugins'));
});
// 合并requirejs和markdown为一个文件
gulp.task('concatMarkdownJs', function() {
var jss = [
'js/require.js',
'md/main.min.js',
];
for(var i in jss) {
jss[i] = base + '/' + jss[i];
}
return gulp
.src(jss)
.pipe(uglify()) // 压缩
.pipe(concat('markdown.min.js'))
.pipe(gulp.dest(base + '/js'));
});
// / 合并requirejs和markdown为一个文件
gulp.task('concatMarkdownJsV2', function() {
var jss = [
'js/require.js',
'md/main-v2.min.js',
];
for(var i in jss) {
jss[i] = base + '/' + jss[i];
}
return gulp
.src(jss)
.pipe(uglify()) // 压缩
.pipe(concat('markdown-v2.min.js'))
.pipe(gulp.dest(base + '/js'));
});
// note-dev.html -> note.html, 替换css, js
// TODO 加?t=2323232, 强制浏览器更新, 一般只需要把app.min.js上加
gulp.task('devToProHtml', function() {
return gulp
.src(noteDev)
.pipe(replace(/<!-- dev -->[.\s\S]+?<!-- \/dev -->/g, '')) // 把dev 去掉
.pipe(replace(/<!-- pro_dep_js -->/, '<script src="/js/dep.min.js"></script>')) // 替换
.pipe(replace(/<!-- pro_app_js -->/, '<script src="/js/app.min.js"></script>')) // 替换
// .pipe(replace(/<!-- pro_markdown_js -->/, '<script src="/js/markdown.min.js"></script>')) // 替换
.pipe(replace(/<!-- pro_markdown_js -->/, '<script src="/js/markdown-v2.min.js"></script>')) // 替换
.pipe(replace('/tinymce/tinymce.js', '/tinymce/tinymce.full.min.js')) // 替换
.pipe(replace(/<!-- pro_tinymce_init_js -->/, "var tinyMCEPreInit = {base: '/public/tinymce', suffix: '.min'};")) // 替换
.pipe(replace(/plugins\/main.js/, "plugins/main.min.js")) // 替换
// 连续两个空行换成一个空行
.pipe(replace(/\r\n\r\n/g, '\r\n'))
.pipe(replace(/\r\n\r\n/g, '\r\n'))
.pipe(replace(/\r\n\r\n/g, '\r\n'))
.pipe(replace(/\r\n\r\n/g, '\r\n'))
.pipe(replace(/\r\n\r\n/g, '\r\n'))
.pipe(replace(/\r\n\r\n/g, '\r\n'))
.pipe(replace('console.log(o);', ''))
.pipe(replace('console.trace(o);', ''))
// .pipe(minifyHtml()) // 不行, 压缩后golang报错
.pipe(rename('note.html'))
.pipe(gulp.dest(noteProBase));
});
// 只获取需要js i18n的key
var path = require('path');
gulp.task('i18n', function() {
var keys = {};
var reg = /getMsg\(["']+(.+?)["']+/g;
// {rule: "required", msg: "inputNewPassword"},
var reg2 = /msg: ?"?([0-9a-zA-Z]*)"?/g;
function getKey(data) {
while(ret = reg.exec(data)) {
keys[ret[1]] = 1;
}
while(ret2 = reg2.exec(data)) {
keys[ret2[1]] = 1;
}
}
// 先获取需要的key
function ls(ff) {
var files = fs.readdirSync(ff);
for(fn in files) {
var fname = ff + path.sep + files[fn];
var stat = fs.lstatSync(fname);
if(stat.isDirectory() == true) {
ls(fname);
}
else {
if ((fname.indexOf('.html') > 0 || fname.indexOf('.js') > 0)) {
// console.log(fname);
// if (fname.indexOf('min.js') < 0) {
var data = fs.readFileSync(fname, "utf-8");
// 得到getMsg里的key
getKey(data);
// }
}
}
}
}
console.log('parsing used keys');
ls(base + '/admin');
ls(base + '/blog');
ls(base + '/md');
ls(base + '/js');
ls(base + '/album');
ls(base + '/libs');
ls(base + '/member');
ls(base + '/tinymce');
ls(leanoteBase + '/app/views');
console.log('parsed');
var langs = {}; // zh-cn: 1
// msg.zh
function getAllMsgs(fname) {
var msg = {};
var data = fs.readFileSync(fname, "utf-8");
var lines = data.split('\n');
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
// 忽略注释
if (line[0] == '#' || line[1] == '#') {
continue;
}
var lineArr = line.split('=');
if (lineArr.length >= 2) {
var key = lineArr[0];
lineArr.shift();
msg[key] = lineArr.join('=');
// msg[lineArr[0]] = lineArr[1];
}
}
return msg;
}
// 得到所有的语言的后缀
// 返回{en-us: 1, }
function getAllLangs() {
var langs = {};
var files = fs.readdirSync(messagesPath);
for(fn in files) {
var fname = files[fn];
if (fname.indexOf('-') > 0) {
langs[fname] = 1;
}
}
return langs;
}
// msg.zh, msg.js
function genI18nJsFile(targetFilename, lang, fromFilenames, keys) {
var msgs = {};
fromFilenames.forEach(function (name) {
var tmpMsgs = getAllMsgs(leanoteBase + '/messages/' + lang + '/' + name + '.conf');
for (var i in tmpMsgs) {
msgs[i] = tmpMsgs[i];
}
});
var toMsgs = {};
for (var i in msgs) {
// 只要需要的
if (i in keys) {
toMsgs[i] = msgs[i];
}
}
var str = 'var MSG=' + JSON.stringify(toMsgs) + ';';
str += 'function getMsg(key, data) {var msg = MSG[key];if(msg) {if(data) {if(!isArray(data)) {data = [data];}' +
'for(var i = 0; i < data.length; ++i) {' +
'msg = msg.replace("%s", data[i]);' +
'}' +
'}' +
'return msg;' +
'}' +
'return key;' +
'}';
// 写入到文件中
var toFilename = targetFilename + '.' + lang + '.js';
fs.writeFile(base + '/js/i18n/' + toFilename, str);
}
function genTinymceLang(lang) {
var msgs = getAllMsgs(leanoteBase + 'messages/' + lang + '/tinymce_editor.conf');
var str = 'tinymce.addI18n("' + lang + '",' + JSON.stringify(msgs) + ');';
fs.writeFile(base + '/tinymce/langs/' + lang + '.js', str);
}
var langs = getAllLangs();
for (var lang in langs) {
genI18nJsFile('blog', lang, ['blog'], keys);
genI18nJsFile('msg', lang, ['msg', 'member', 'markdown', 'album'], keys);
genTinymceLang(lang);
}
});
// 合并album需要的js
gulp.task('concatAlbumJs', function() {
/*
gulp.src(base + '/album/js/main.js')
.pipe(uglify()) // 压缩
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest(base + '/album/js/'));
*/
gulp.src(base + '/album/css/style.css')
.pipe(rename({suffix: '-min'}))
.pipe(minifycss())
.pipe(gulp.dest(base + '/album/css'));
var jss = [
'js/jquery-1.9.0.min.js',
'js/bootstrap-min.js',
'js/plugins/libs-min/fileupload.js',
'js/jquery.pagination.js',
'album/js/main.js',
];
for(var i in jss) {
jss[i] = base + '/' + jss[i];
}
return gulp
.src(jss)
.pipe(uglify()) // 压缩
.pipe(concat('main.all.js'))
.pipe(gulp.dest(base + '/album/js'));
});
// tinymce
// please set the right path on your own env
var tinymceBase = '/Users/life/leanote/leanote-tools/tinymce_4.1.9_leanote_public';
gulp.task('tinymce', function() {
// 先清理
fs.unlink(tinymceBase + '/js/tinymce/tinymce.dev.js');
fs.unlink(tinymceBase + '/js/tinymce/tinymce.jquery.dev.js');
fs.unlink(tinymceBase + '/js/tinymce/tinymce.full.js');
fs.unlink(tinymceBase + '/js/tinymce/tinymce.full.min.js');
var cp = require('child_process');
var bundleCmd = 'grunt bundle --themes leanote --plugins autolink,link,leaui_image,leaui_mindmap,lists,hr,paste,searchreplace,leanote_nav,leanote_code,tabfocus,table,directionality,textcolor';
// build
cp.exec('grunt minify', {cwd: tinymceBase}, function(err, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
// 将所有都合并成一起
cp.exec(bundleCmd, {cwd: tinymceBase}, function(err, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
});
});
});
// 合并css, 无用
// Deprecated
gulp.task('concatCss', function() {
return gulp
.src([markdownRaw + '/css/default.css', markdownRaw + '/css/md.css'])
.pipe(concat('all.css'))
.pipe(gulp.dest(markdownMin));
});
// mincss
var minifycss = require('gulp-minify-css');
gulp.task('minifycss', function() {
gulp.src(base + '/css/bootstrap.css')
.pipe(rename({suffix: '-min'}))
.pipe(minifycss())
.pipe(gulp.dest(base + '/css'));
gulp.src(base + '/css/font-awesome-4.2.0/css/font-awesome.css')
.pipe(rename({suffix: '-min'}))
.pipe(minifycss())
.pipe(gulp.dest(base + '/css/font-awesome-4.2.0/css'));
gulp.src(base + '/css/zTreeStyle/zTreeStyle.css')
.pipe(rename({suffix: '-min'}))
.pipe(minifycss())
.pipe(gulp.dest(base + '/css/zTreeStyle'));
gulp.src(base + '/md/themes/default.css')
.pipe(rename({suffix: '-min'}))
.pipe(minifycss())
.pipe(gulp.dest(base + '/md/themes'));
gulp.src(base + '/js/contextmenu/css/contextmenu.css')
.pipe(rename({suffix: '-min'}))
.pipe(minifycss())
.pipe(gulp.dest(base + '/js/contextmenu/css'));
// theme
// 用codekit
var as = ['default', 'simple', 'writting', /*'writting-overwrite', */ 'mobile'];
/*
for(var i = 0; i < as.length; ++i) {
gulp.src(base + '/css/theme/' + as[i] + '.css')
.pipe(minifycss())
.pipe(gulp.dest(base + '/css/theme'));
}
*/
});
gulp.task('concat', ['concatDepJs', 'concatAppJs', /* 'concatMarkdownJs', */'concatMarkdownJsV2']);
gulp.task('html', ['devToProHtml']);
gulp.task('default', ['concat', 'plugins', 'minifycss', 'i18n', 'concatAlbumJs', 'html']);

26
LICENSE
View File

@@ -1,5 +1,25 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
LEANOTE - NOT JUST A NOTEPAD!
Copyright 2014-2015 by the contributors
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Leanote is licensed under the GPL v2.
life(life@leanote.com, lifephp@gmail.com)
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
@@ -336,4 +356,4 @@ This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Public License instead of this License.

297
README.md
View File

@@ -1,212 +1,189 @@
[中文](https://github.com/leanote/leanote#1-介绍)
# Leanote
[![Build Status](https://travis-ci.org/leanote/leanote.svg)](https://travis-ci.org/leanote/leanote)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/leanote/leanote?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
## 1. Introduction
Leanote, your own cloud note.
Leanote, not just a notepad!
![leanote.png](leanote.png "")
**Some Features**
**Highlighted Features**
* Knowledge: Manage your knowledge in leanote. leanote contains the tinymce editor and a markdown editor, just enjoy yourself writing.
* Share: Share your knowledge with your friends in leanote. You can invite your friends to join your notepad in the cloud so you can share knowledge.
* Cooperation: Collaborate with friends to improve your skills.
* Blog: Publish your knowledge and make leanote your blog.
* Note-taking made easy: Leanote incorporates a clean and intuitive interface, the `tinymce` rich-text editor and a dedicated *markdown* editor, making your writing/typing more efficient and enjoyable. For more advanced users, we even offer `Vim` and `Emacs` writing modes to help boost your writing speed to another level.
* Knowledge management: The flexible and versatile notebook-note-tagging system of Leanote makes it an ideal tool for knowledge management.
* Sharing: Share your knowledge, thoughts and experiences with friends via Leanote. Invite your friends to join your notepad in the cloud.
* Cooperating: Collaborate with colleagues to improve skills, fertilize ideas and brainstorm on the fly.
* Blogging: Publish your work and make Leanote your personal blog.
## 2. Why we created leanote
To be honest, our inspiration comes from Evernote. We use Evernote to manage our knowledge everyday. But we find that:
* Evernote's editor can't meet our needs, it does not have document navigation, it does not render code properly (as a programmer, syntax highlighted code rendering is a basic need), it cannot resize images and so forth
* We like markdown, but Evernote does not support it.
* We want to share our knowledge, so all of us have our blogs (e.g. on Wordpress) and our Evernote accounts, but why can not those two be one!
**Other Features**
* Markdown syntax support
* Distraction-free writing mode
* `Vim` and `Emacs` editing mode
* Export notes to PDFs
* Batch note operation
* Customizable themes for blogging
## 2. Why we create Leanote
We have been using the popular note-taking software/service `Evernote` as our knowledge management tool on a daily basis. Benefited from and inspired by `Evernote`, we decided to create a brand-new tool that provides everything `Evernote` has to offer, plus a bunch of new features that `Evernote` failed to deliver, such as:
* A more powerful editor: `Evernote`'s editor lacks the functionalities of **document navigation**, **syntax based code rendering** (as a programmer, syntax highlighted code rendering is a necessity), **image resizing** and so forth.
* Everybody loves *markdown*, however `Evernote` simply wouldn't add it despite of years' of requests from users. So we will do the favor and bring a *markdown* enabled editor to you, guess what, it is also rendered in real-time!
* If you a developer and miss the `Vim` or `Emacs` ways of writing, we offer you the choice of `Vim` and `Emacs` editting modes. Equipped with *markdown* syntax for text formatting, you will never need to touch your mouse while writing.
* We love managing knowledge and thoughts as much as sharing them, so everybody has their own note account (`Evernote`, `Onenote`, `Google doc`, `Wiz note` etc.) and social media account (`Facebook`, `Wordpress`, blogs, etc.). But why cant those two be one? Leanote makes this first step to bridge the private note-taking and public knowledge sharing seamlessly.
* A complete and all-platform (sorry Windows phone) covering software suite: that includes Leanote Web & Server (this repository), [Desktop app](https://github.com/leanote/desktop-app), [iOS](https://github.com/leanote/leanote-ios), [Android](https://github.com/leanote/leanote-android). And they are all open source!
* ......
## 3. How to install leanote
## 3. How to get Leanote
### 3.1. Download leanote
The Leanote software suite contains: Leanote Web & Server (this repository), [Desktop app](https://github.com/leanote/desktop-app), [iOS](https://github.com/leanote/leanote-ios), [Android](https://github.com/leanote/leanote-android).
Leanote V0.3 has been released. Binaries:
Interested in our product and want to try it out from your web browser? Welcome to sign up on https://leanote.com.
* Linux: [leanote-linux-v0.3.bin.tar.gz](https://github.com/leanote/leanote/releases/download/0.3/leanote-linux-v0.3.bin.tar.gz)
* MacOS X: [leanote-mac-v0.3.bin.tar.gz](https://github.com/leanote/leanote/releases/download/0.3/leanote-mac-v0.3.bin.tar.gz)
Feeling suspicious about how those note-taking companies treat your personal data? You can install Leanote on your server, and use Leanote App (Desktop, iOS, Android) to sync notes with your self-hosted server.
### 3.2. Install MongoDB
More information about how to install Leanote please see:
Leanote is written in go using [revel](https://revel.github.io/) and [MongoDB](https://www.mongodb.org). Thus, you need to first install MongoDB.
* Leanote binary installation tutorial:
* [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En))
* [Mac and Linux](https://github.com/leanote/leanote/wiki/leanote-binary-installation-on-Mac-and-Linux-(En))
* Leanote source installation tutorial:
* [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En))
* [Mac and Linux](https://github.com/leanote/leanote/wiki/Leanote-source-installation-on-Mac-and-Linux-(En))
For more tips please have a look at [our wiki](https://github.com/leanote/leanote/wiki/Install-Mongodb)
## 4. Documentation
### 3.3. Import initial MongoDB data
The mongodb data is in `[PATH_TO_LEANOTE]/mongodb_backup/leanote_install_data`
```
$> mongorestore -h localhost -d leanote --directoryperdb PATH_TO_LEANOTE/mongodb_backup/leanote_install_data
```
The initial database contains three users:
```
user1 username: leanote, password: abc123
user2 username: admin, password: abc123
user3 username: demo@leanote.com, password: demo@leanote.com (this user is for demo)
```
### 3.4. Configuration
Modify `[PATH_TO_LEANOTE]/conf/app.conf`. Available configuration options are:
``mongodb`` **required**
```Shell
db.host=localhost
db.port=27017
db.dbname=leanote
db.username=
db.password=
```
``http.port``
Default is 80
``site.url``
Default is `http://localhost`, you must edit this when hosting leanote anywhere else. This is used when uploading images.
``email``
For password recovery mails
``adminUsername``
Default is `admin`. The landing page is the admin user's blog.
For more infomation please see `app/app.conf` and the [revel manuals](https://revel.github.io/)
### 3.5. Run leanote
```
$> cd PATH_TO_LEANOTE/bin
$> sudo sh run.sh
```
## 4. How to develop leanote
Please see [How-to-develop-leanote](https://github.com/leanote/leanote/wiki/How-to-develop-leanote-%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91leanote)
Please see [wiki](https://github.com/leanote/leanote/wiki) for detailed instruction on how to install Leanote on various platforms, trouble shooting and configuration explanations.
## 5. Contributors
Thank you to all the [contributors](https://github.com/leanote/leanote/graphs/contributors) on
this project. Your help is much appreciated.
## 5. How to develop Leanote
## 6. Contributing
If you are a developer yourself and feel like to build on top of Leanote, please refer to [How-to-develop-leanote](https://github.com/leanote/leanote/wiki/How-to-develop-leanote-%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91leanote).
Please fork this repository and contribute back using [pull requests](https://github.com/leanote/leanote/pulls).
## Discussion
[leanote google group](https://groups.google.com/forum/#!forum/leanote)
## 6. Contributions
Like or dislike Leanote, please leave your comments and suggestions to help us improve it.
If you encounter any issue, we suggest you first do a search in the issues section to see whether a solution already exists, or open up a new one otherwise.
Wed like to acknowledge the contributions made by our [developers and contributors](https://github.com/leanote/leanote/graphs/contributors) to
this project. Leanote wont exist without your hard work. Your help is much appreciated.
## 7. Join us
Please feel free to fork this repository and contribute back using [pull requests](https://github.com/leanote/leanote/pulls).
If you find any problems or have any good ideas, feature requests, please submit here [issues](https://github.com/leanote/leanote/issues).
## 8. Donation
If you like our product, consider supporting us via [donate us](http://leanote.org/#donate).
We acknowledge the donations make by all the [donators](http://leanote.leanote.com/post/leanote-donation-list).
## 9. Related projects
* [Leanote Desktop App](https://github.com/leanote/desktop-app), [Download](http://app.leanote.com)
* [Leanote iOS](https://github.com/leanote/leanote-ios), [Download From App Store](https://itunes.apple.com/en/app/leanote/id1022302858?mt=8)
* [Leanote Android](https://github.com/leanote/leanote-android), development phase
You are welcome to join us.
## 10. Contacts
* Email: leanote@leanote.com
* [Leanote BBS](http://bbs.leanote.com)
* [Leanote Google Group](https://groups.google.com/forum/#!forum/leanote)
* QQ Groups: 326073529, 256076853, 158716820
-----------------------------------------------------------------------
# Leanote
## 1. 介绍
Leanote, 你的私人云笔记!
Leanote, 不只是笔记!
**特性**
* 知识管理: 通过leanote来管理知识, leanote有易操作的界面, 包含两款编辑器tinymce和markdown. 在leanote, 你可以尽情享受写作.
* 分享: 你也可以通过分享知识给好友, 让好友拥有你的知识.
* 协作: 在分享的同时也可以与好友一起协作知识.
* 博客: leanote也可以作为你的博客, 将知识公开成博客, 让leanote把你的知识传播的更远!
* 高效笔记Leanote 有易操作的界面, 包含一款富文本编辑器和Markdown编辑器让您的笔记记录更轻松和高效。对高阶用户我们还提供`Vim``Emacs` 编辑模式,助推你的写作速度更上层楼。
* 知识管理: Leanote 灵活而强大的“笔记本-笔记-标签”系统,让它成为你个人知识管理的利器。
* 分享: 你可以通过Leanote同好友分享知识、想法和经历, 邀请好友加入你的笔记簿,通过云端交流信息。
* 协作: Leanote协助你与同事之间相互协作激荡新思路随时随地头脑风暴。
* 博客: Leanote也可以作为你的个人博客, 把你的知识传播的更远!
## 2. 为什么我们要创建leanote?
说实话, 我们曾是evernote的忠实粉丝, 但是我们也发现evernote的不足:
* leanote的编辑器不能满足我们的需求, 不能贴代码(格式会乱掉, 作为程序员, 代码是我们的基本需求啊), 图片不能缩放.
* 我们是markdown的爱好者, 可是evernote竟然没有.
* 我们也想将知识公开, 所以我们有自己的博客, 如wordpress, 但为什么这两者不能合二为一呢?
**其它特性**
* 支持Markdown编辑
* 写作模式
* `Vim``Emacs` 编辑模式
* 支持PDF导出
* 支持批量操作
* 博客自定义主题, 实现高度定制化
## 2. 为什么我们要创建Leanote?
我们都曾是`Evernote`的忠实粉丝, 一直以来`Evernote`都是我们日常知识管理的有效工具。于是我们决定重新创造一款工具,提供`Evernote`所能提供的功能,同时弥补`Evernote`的不足,比如:
* 功能更强的文本编辑器:`Evernote`的编辑器不能满足我们的需求, 不能实现文档导航、不能贴代码(格式会乱掉, 作为程序员, 代码是我们的基本需求啊), 图片不能缩放等。
* `Evernote` 不支持所有人都喜爱的markdown语法于是我们为Leanote配备了一款可以实时渲染的markdown编辑器。
* 如果你是一名开发者,觉得手指怀念`Vim``Emacs` 了,那么我们还提供给你`Vim``Emacs` 写作模式,配合*markdown*的格式编辑,写作的时候再也不用去碰鼠标了。
* 知识积累和知识分享同样重要,因此大家都有自己的笔记账号和社交账号。但为什么这两者不能合二为一呢? Leanote 做到了将二者无缝衔接。
* 一套完整的、全平台覆盖的软件套装包括了web、桌面、安卓、IOS设备而且全部开源
* 还有...
## 3.安装leanote
leanote是一款私有云笔记, 你可以下载它安装在自己的服务器上, 当然也可以在 http://leanote.com 上注册.
## 3. 获取Leanote
### 3.1. 下载leanote
Leanote云笔记产品包括: Leanote Web & Server(即本仓库), 桌面客户端, IOS, android. 4端全部开源!
Leanote V0.3 已发布, 二进制文件(暂时没有windows版的):
如果想试用我们的产品,欢迎在 https://leanote.com 上注册, Leanote团队为你提供稳定可靠的服务。
担心服务厂商如何处理你的个人数据吗你可以下载Leanote安装在自己的服务器上, 通过Leanote客户端连接与自建服务同步数据。
* Linux: [leanote-linux-v0.3.bin.tar.gz](https://github.com/leanote/leanote/releases/download/0.3/leanote-linux-v0.3.bin.tar.gz)
* MacOS X: [leanote-mac-v0.3.bin.tar.gz](https://github.com/leanote/leanote/releases/download/0.3/leanote-mac-v0.3.bin.tar.gz)
这里详细整理了Leanote二进版和Leanote开发版的安装教程, 请移步至:
### 3.2. 安装 MongodbDB
* Leanote二进制详细安装教程:
* [Windows](https://github.com/leanote/leanote/wiki/Leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B---Windows)
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
* Leanote源码详细安装教程:
* [Windows](https://github.com/leanote/leanote/wiki/Leanote-%E6%BA%90%E7%A0%81%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B----Windows)
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E5%BC%80%E5%8F%91%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
Leanote是由golang(使用[revel](https://revel.github.io/)框架 和 [MongoDB](https://www.mongodb.org)数据库), 你需要先安装Mongodb.
## 4. 相关文档
安装MongodbDB, 导入数据更多细节请查看: [wiki](https://github.com/leanote/leanote/wiki/Install-Mongodb)
更多详细的安装说明、问题处理和配置说明文档,请查看 [wiki](https://github.com/leanote/leanote/wiki)。
### 3.3. 导入初始数据
## 5. 如何对Leanote进行二次开发
MongodbDB初始数据在 `[PATH_TO_LEANOTE]/mongodb_backup/leanote_install_data`
如果您有兴趣基于Leanote二次开发请查看 [How-to-develop-Leanote](https://github.com/leanote/leanote/wiki/How-to-develop-leanote-%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91leanote)。
```
$> mongorestore -h localhost -d leanote --directoryperdb PATH_TO_LEANOTE/mongodb_backup/leanote_install_data
```
## 6. 贡献者
初始数据包含三个用户:
在此对向Leanote贡献力量的[贡献者们](https://github.com/leanote/leanote/graphs/contributors) 表示感谢。Leanote因有你们而更完美!
```
user1 username: leanote, password: abc123
user2 username: admin, password: abc123
user3 username: demo@leanote.com, password: demo@leanote.com (为体验使用)
```
## 7. 加入我们
### 3.4. 配置
欢迎提交[pull requests](https://github.com/leanote/leanote/pulls) 到Leanote。
修改 `[PATH_TO_LEANOTE]/conf/app.conf`. 有以下选项:
有任何问题或建议, 请先搜索[issue](https://github.com/leanote/leanote/issues)区是否已经有解决方法。如果没有欢迎提交新issue。
``mongodb`` **必须配置!**
Leanote还有很多问题, 如果你喜欢它, 欢迎加入我们一起完善Leanote。
```Shell
db.host=localhost
db.port=27017
db.dbname=leanote
db.username=
db.password=
```
## 8. 捐赠
``http.port``
如果您喜欢我们的产品,请考虑支持我们, [捐赠Leanote](http://leanote.org/#donate)。
默认为 80
感谢[这些捐赠者](http://leanote.leanote.com/post/leanote-donation-list), 谢谢你们的鼓励, Leanote会一直坚持!
``site.url``
## 9. 其它相关项目
* [Leanote Desktop App](https://github.com/leanote/desktop-app), [下载地址](http://app.leanote.com)
* [Leanote iOS](https://github.com/leanote/leanote-ios), [从App Store下载](https://itunes.apple.com/zn/app/leanote/id1022302858?mt=8)
* [Leanote Android](https://github.com/leanote/leanote-android), 开发阶段
默认是 `http://localhost`, 这会在上传图片后的图片路径中用户, 还有发邮件, 找回密码验证邮箱时用到.
欢迎加入我们!
``email``
## 联系&加入我们
* Email: leanote@leanote.com
* [Leanote 社区](http://bbs.leanote.com)
* [QQ群](http://leanote.leanote.com/post/Leanote-groups)
* [Leanote Google Group](https://groups.google.com/forum/#!forum/leanote)
找回密码和验证邮箱时使用
``adminUsername``
默认是 `admin`. 首页即为该用户的博客
更多配置请查看 `app/app.conf` 和 [revel 手册](https://revel.github.io/)
### 3.5. 运行leanote
```
$> cd PATH_TO_LEANOTE/bin
$> sudo sh run.sh
```
## 4. 如何对leanote进行二次开发
请查看 [How-to-develop-leanote](https://github.com/leanote/leanote/wiki/How-to-develop-leanote-%E5%A6%82%E4%BD%95%E5%BC%80%E5%8F%91leanote)
## 5. 贡献者
多谢 [贡献者](https://github.com/leanote/leanote/graphs/contributors) 的贡献, leanote因有你们而更完美!
## 6. 加入我们
欢迎提交[pull requests](https://github.com/leanote/leanote/pulls) 到leanote.
leanote还有很多问题, 如果你喜欢它, 欢迎加入我们一起完善leanote.
## 讨论
[leanote google group](https://groups.google.com/forum/#!forum/leanote)

20
Vendor.md Normal file
View File

@@ -0,0 +1,20 @@
# Modifications
* revel.go
```
RevelImportPath = "github.com/leanote/leanote/vendor/github.com/revel/revel"
```
# RUN
```
go install github.com/leanote/leanote/cmd/leanote_revel
leanote_revel run github.com/leanote/leanote
```
# build leanote, 在当前目录生成了leanote二进制文件
go build -o ./leanote github.com/leanote/leanote/app/tmp
# 运行leanote, 其中-importPath是必须的
./leanote -importPath=github.com/leanote/leanote -runMode=prod -port=9000

19
app/cmd/main.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import (
"fmt"
"github.com/revel/cmd/harness"
"github.com/revel/revel"
)
func main() {
// go run main.go
// 生成routes.go, main.go
revel.Init("", "github.com/leanote/leanote", "")
_, err := harness.Build() // ok, err
if err != nil {
panic(err)
}
fmt.Println("Ok")
// panicOnError(reverr, "Failed to build")
}

View File

@@ -2,45 +2,51 @@ package controllers
import (
"github.com/revel/revel"
// "encoding/json"
// "encoding/json"
"github.com/leanote/leanote/app/info"
"gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
)
// Album controller
type Album struct {
BaseController
}
// 图片管理, iframe
func (c Album) Index() revel.Result {
c.SetLocale()
return c.RenderTemplate("album/index.html")
}
// all albums by userId
func (c Album) GetAlbums() revel.Result {
re := albumService.GetAlbums(c.GetUserId())
return c.RenderJson(re)
return c.RenderJSON(re)
}
func (c Album) DeleteAlbum(albumId string) revel.Result {
re, msg := albumService.DeleteAlbum(c.GetUserId(), albumId)
return c.RenderJson(info.Re{Ok: re, Msg: msg})
return c.RenderJSON(info.Re{Ok: re, Msg: msg})
}
// add album
func (c Album) AddAlbum(name string) revel.Result {
album := info.Album{
AlbumId: bson.NewObjectId(),
Name: name,
Seq: -1,
UserId: c.GetObjectUserId()}
Name: name,
Seq: -1,
UserId: c.GetObjectUserId()}
re := albumService.AddAlbum(album)
if(re) {
return c.RenderJson(album)
if re {
return c.RenderJSON(album)
} else {
return c.RenderJson(false)
return c.RenderJSON(false)
}
}
// update alnum name
func (c Album) UpdateAlbum(albumId, name string) revel.Result {
return c.RenderJson(albumService.UpdateAlbum(albumId, c.GetUserId(), name))
}
return c.RenderJSON(albumService.UpdateAlbum(albumId, c.GetUserId(), name))
}

View File

@@ -0,0 +1,227 @@
package controllers
import (
"github.com/revel/revel"
// "encoding/json"
"archive/tar"
"compress/gzip"
"fmt"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"io"
"io/ioutil"
"os"
"strings"
"time"
)
// 附件
type Attach struct {
BaseController
}
// 上传附件
func (c Attach) UploadAttach(noteId string) revel.Result {
re := c.uploadAttach(noteId)
return c.RenderJSON(re)
}
func (c Attach) uploadAttach(noteId string) (re info.Re) {
var fileId = ""
var resultMsg = "error" // 错误信息
var Ok = false
var fileInfo info.Attach
re = info.NewRe()
defer func() {
re.Id = fileId // 只是id, 没有其它信息
re.Msg = resultMsg
re.Ok = Ok
re.Item = fileInfo
}()
// 判断是否有权限为笔记添加附件
if !shareService.HasUpdateNotePerm(noteId, c.GetUserId()) {
return re
}
file, handel, err := c.Request.FormFile("file")
if err != nil {
return re
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return re
}
// > 5M?
maxFileSize := configService.GetUploadSize("uploadAttachSize")
if maxFileSize <= 0 {
maxFileSize = 1000
}
if float64(len(data)) > maxFileSize*float64(1024*1024) {
resultMsg = fmt.Sprintf("The file's size is bigger than %vM", maxFileSize)
return re
}
// 生成上传路径
// filePath := "files/" + c.GetUserId() + "/attachs"
newGuid := NewGuid()
filePath := "files/" + GetRandomFilePath(c.GetUserId(), newGuid) + "/attachs"
dir := revel.BasePath + "/" + filePath
err = os.MkdirAll(dir, 0755)
if err != nil {
return re
}
// 生成新的文件名
filename := handel.Filename
_, ext := SplitFilename(filename) // .doc
filename = newGuid + ext
toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
return re
}
// add File to db
fileType := ""
if ext != "" {
fileType = strings.ToLower(ext[1:])
}
filesize := GetFilesize(toPath)
fileInfo = info.Attach{Name: filename,
Title: handel.Filename,
NoteId: bson.ObjectIdHex(noteId),
UploadUserId: c.GetObjectUserId(),
Path: filePath + "/" + filename,
Type: fileType,
Size: filesize}
id := bson.NewObjectId()
fileInfo.AttachId = id
fileId = id.Hex()
Ok, resultMsg = attachService.AddAttach(fileInfo, false)
if resultMsg != "" {
resultMsg = c.Message(resultMsg)
}
fileInfo.Path = "" // 不要返回
if Ok {
resultMsg = "success"
}
return re
}
// 删除附件
func (c Attach) DeleteAttach(attachId string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = attachService.DeleteAttach(attachId, c.GetUserId())
return c.RenderJSON(re)
}
// get all attachs by noteId
func (c Attach) GetAttachs(noteId string) revel.Result {
re := info.NewRe()
re.Ok = true
re.List = attachService.ListAttachs(noteId, c.GetUserId())
return c.RenderJSON(re)
}
// 下载附件
// 权限判断
func (c Attach) Download(attachId string) revel.Result {
attach := attachService.GetAttach(attachId, c.GetUserId()) // 得到路径
path := attach.Path
if path == "" {
return c.RenderText("")
}
fn := revel.BasePath + "/" + strings.TrimLeft(path, "/")
file, _ := os.Open(fn)
return c.RenderBinary(file, attach.Title, revel.Attachment, time.Now()) // revel.Attachment
// return c.RenderFile(file, revel.Attachment) // revel.Attachment
}
func (c Attach) DownloadAll(noteId string) revel.Result {
note := noteService.GetNoteById(noteId)
if note.NoteId == "" {
return c.RenderText("")
}
// 得到文件列表
attachs := attachService.ListAttachs(noteId, c.GetUserId())
if attachs == nil || len(attachs) == 0 {
return c.RenderText("")
}
/*
dir := revel.BasePath + "/files/tmp"
err := os.MkdirAll(dir, 0755)
if err != nil {
return c.RenderText("")
}
*/
filename := note.Title + ".tar.gz"
if note.Title == "" {
filename = "all.tar.gz"
}
dir := revel.BasePath + "/files/attach_all"
if !MkdirAll(dir) {
return c.RenderText("error")
}
// file write
fw, err := os.Create(dir + "/" + filename)
if err != nil {
return c.RenderText("error")
}
// defer fw.Close() // 不需要关闭, 还要读取给用户下载
// gzip write
gw := gzip.NewWriter(fw)
defer gw.Close()
// tar write
tw := tar.NewWriter(gw)
defer tw.Close()
// 遍历文件列表
for _, attach := range attachs {
fn := revel.BasePath + "/" + strings.TrimLeft(attach.Path, "/")
fr, err := os.Open(fn)
fileInfo, _ := fr.Stat()
if err != nil {
return c.RenderText("")
}
defer fr.Close()
// 信息头
h := new(tar.Header)
h.Name = attach.Title
h.Size = fileInfo.Size()
h.Mode = int64(fileInfo.Mode())
h.ModTime = fileInfo.ModTime()
// 写信息头
err = tw.WriteHeader(h)
if err != nil {
panic(err)
}
// 写文件
_, err = io.Copy(tw, fr)
if err != nil {
panic(err)
}
} // for
// tw.Close()
// gw.Close()
// fw.Close()
// file, _ := os.Open(dir + "/" + filename)
// fw.Seek(0, 0)
return c.RenderBinary(fw, filename, revel.Attachment, time.Now()) // revel.Attachment
}

View File

@@ -1,9 +1,11 @@
package controllers
import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"strings"
// "strconv"
)
// 用户登录/注销/找回密码
@@ -14,128 +16,175 @@ type Auth struct {
//--------
// 登录
func (c Auth) Login(email string) revel.Result {
c.RenderArgs["title"] = c.Message("login")
c.RenderArgs["subTitle"] = c.Message("login")
c.RenderArgs["email"] = email
c.RenderArgs["openRegister"] = openRegister
func (c Auth) Login(email, from string) revel.Result {
c.ViewArgs["title"] = c.Message("login")
c.ViewArgs["subTitle"] = c.Message("login")
c.ViewArgs["email"] = email
c.ViewArgs["from"] = from
c.ViewArgs["openRegister"] = configService.IsOpenRegister()
sessionId := c.Session.ID()
if sessionService.LoginTimesIsOver(sessionId) {
c.ViewArgs["needCaptcha"] = true
}
c.SetLocale()
if c.Has("demo") {
c.RenderArgs["demo"] = true
c.RenderArgs["email"] = "demo@leanote.com"
c.ViewArgs["demo"] = true
c.ViewArgs["email"] = "demo@leanote.com"
}
return c.RenderTemplate("home/login.html")
}
func (c Auth) DoLogin(email, pwd string) revel.Result {
userInfo := authService.Login(email, pwd)
if userInfo.Email != "" {
// 为了demo和register
func (c Auth) doLogin(email, pwd string) revel.Result {
sessionId := c.Session.ID()
var msg = ""
userInfo, err := authService.Login(email, pwd)
if err != nil {
// 登录错误, 则错误次数++
msg = "wrongUsernameOrPassword"
} else {
c.SetSession(userInfo)
// 必须要redirect, 不然用户刷新会重复提交登录信息
// return c.Redirect("/")
return c.RenderJson(info.Re{Ok: true})
sessionService.ClearLoginTimes(sessionId)
return c.RenderJSON(info.Re{Ok: true})
}
// return c.RenderTemplate("login.html")
return c.RenderJson(info.Re{Ok: false, Msg: c.Message("wrongUsernameOrPassword")})
return c.RenderJSON(info.Re{Ok: false, Item: sessionService.LoginTimesIsOver(sessionId), Msg: c.Message(msg)})
}
func (c Auth) DoLogin(email, pwd string, captcha string) revel.Result {
sessionId := c.Session.ID()
var msg = ""
// > 5次需要验证码, 直到登录成功
if sessionService.LoginTimesIsOver(sessionId) && sessionService.GetCaptcha(sessionId) != captcha {
msg = "captchaError"
} else {
userInfo, err := authService.Login(email, pwd)
if err != nil {
// 登录错误, 则错误次数++
msg = "wrongUsernameOrPassword"
sessionService.IncrLoginTimes(sessionId)
} else {
c.SetSession(userInfo)
sessionService.ClearLoginTimes(sessionId)
return c.RenderJSON(info.Re{Ok: true})
}
}
return c.RenderJSON(info.Re{Ok: false, Item: sessionService.LoginTimesIsOver(sessionId), Msg: c.Message(msg)})
}
// 注销
func (c Auth) Logout() revel.Result {
sessionId := c.Session.ID()
sessionService.Clear(sessionId)
c.ClearSession()
return c.Redirect("/login")
}
// 体验一下
func (c Auth) Demo() revel.Result {
c.DoLogin("demo@leanote.com", "demo@leanote.com")
return c.Redirect("/note")
email := configService.GetGlobalStringConfig("demoUsername")
pwd := configService.GetGlobalStringConfig("demoPassword")
userInfo, err := authService.Login(email, pwd)
if err != nil {
return c.RenderJSON(info.Re{Ok: false})
} else {
c.SetSession(userInfo)
return c.Redirect("/note")
}
return nil
}
//--------
// 注册
func (c Auth) Register() revel.Result {
if !openRegister {
func (c Auth) Register(from, iu string) revel.Result {
if !configService.IsOpenRegister() {
return c.Redirect("/index")
}
c.RenderArgs["title"] = c.Message("register")
c.RenderArgs["subTitle"] = c.Message("register")
c.SetLocale()
c.ViewArgs["from"] = from
c.ViewArgs["iu"] = iu
c.ViewArgs["title"] = c.Message("register")
c.ViewArgs["subTitle"] = c.Message("register")
return c.RenderTemplate("home/register.html")
}
func (c Auth) DoRegister(email, pwd string) revel.Result {
if !openRegister {
func (c Auth) DoRegister(email, pwd, iu string) revel.Result {
if !configService.IsOpenRegister() {
return c.Redirect("/index")
}
re := info.NewRe();
if email == "" {
re.Msg = c.Message("inputEmail")
return c.RenderJson(re)
} else if !IsEmail(email) {
re.Msg = c.Message("wrongEmail")
return c.RenderJson(re)
re := info.NewRe()
if re.Ok, re.Msg = Vd("email", email); !re.Ok {
return c.RenderRe(re)
}
// 密码
if pwd == "" {
re.Msg = c.Message("inputPassword")
return c.RenderJson(re)
} else if len(pwd) < 6 {
re.Msg = c.Message("wrongPassword")
return c.RenderJson(re)
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re)
}
email = strings.ToLower(email)
// 注册
re.Ok, re.Msg = authService.Register(email, pwd)
re.Ok, re.Msg = authService.Register(email, pwd, iu)
// 注册成功, 则立即登录之
if re.Ok {
c.DoLogin(email, pwd)
c.doLogin(email, pwd)
}
return c.RenderJson(re)
return c.RenderRe(re)
}
//--------
// 找回密码
func (c Auth) FindPassword() revel.Result {
c.RenderArgs["title"] = c.Message("findPassword")
c.RenderArgs["subTitle"] = c.Message("findPassword")
c.SetLocale()
c.ViewArgs["title"] = c.Message("findPassword")
c.ViewArgs["subTitle"] = c.Message("findPassword")
return c.RenderTemplate("home/find_password.html")
}
func (c Auth) DoFindPassword(email string) revel.Result {
pwdService.FindPwd(email)
re := info.NewRe()
re.Ok = true
return c.RenderJson(re)
return c.RenderJSON(re)
}
// 点击链接后, 先验证之
func (c Auth) FindPassword2(token string) revel.Result {
c.RenderArgs["title"] = c.Message("findPassword")
c.RenderArgs["subTitle"] = c.Message("findPassword")
c.SetLocale()
c.ViewArgs["title"] = c.Message("findPassword")
c.ViewArgs["subTitle"] = c.Message("findPassword")
if token == "" {
return c.RenderTemplate("find_password2_timeout.html")
}
ok, _, findPwd := tokenService.VerifyToken(token, info.TokenPwd);
ok, _, findPwd := tokenService.VerifyToken(token, info.TokenPwd)
if !ok {
return c.RenderTemplate("home/find_password2_timeout.html")
}
c.RenderArgs["findPwd"] = findPwd
c.RenderArgs["title"] = c.Message("updatePassword")
c.RenderArgs["subTitle"] = c.Message("updatePassword")
c.ViewArgs["findPwd"] = findPwd
c.ViewArgs["title"] = c.Message("updatePassword")
c.ViewArgs["subTitle"] = c.Message("updatePassword")
return c.RenderTemplate("home/find_password2.html")
}
// 找回密码修改密码
func (c Auth) FindPasswordUpdate(token, pwd string) revel.Result {
re := info.NewRe();
re.Ok, re.Msg = IsGoodPwd(pwd)
if !re.Ok {
return c.RenderJson(re)
re := info.NewRe()
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re)
}
// 修改之
re.Ok, re.Msg = pwdService.UpdatePwd(token, pwd)
return c.RenderJson(re)
return c.RenderRe(re)
}

View File

@@ -1,12 +1,15 @@
package controllers
import (
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
"encoding/json"
"github.com/leanote/leanote/app/info"
// "io/ioutil"
// "fmt"
"github.com/leanote/leanote/app/lea/i18n"
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
// "fmt"
"bytes"
"math"
"strconv"
"strings"
@@ -17,6 +20,11 @@ type BaseController struct {
*revel.Controller
}
// 覆盖revel.Message
func (c *BaseController) Message(message string, args ...interface{}) (value string) {
return i18n.Message(c.Request.Locale, message, args...)
}
func (c BaseController) GetUserId() string {
if userId, ok := c.Session["UserId"]; ok {
return userId
@@ -54,27 +62,49 @@ func (c BaseController) GetUsername() string {
// 得到用户信息
func (c BaseController) GetUserInfo() info.User {
if userId, ok := c.Session["UserId"]; ok && userId != "" {
notebookWidth, _ := strconv.Atoi(c.Session["NotebookWidth"])
noteListWidth, _ := strconv.Atoi(c.Session["NoteListWidth"])
user := info.User{UserId: bson.ObjectIdHex(userId),
Email: c.Session["Email"],
Username: c.Session["Username"],
UsernameRaw: c.Session["UsernameRaw"],
Theme: c.Session["Theme"],
NotebookWidth: notebookWidth,
NoteListWidth: noteListWidth,
return userService.GetUserInfo(userId)
/*
notebookWidth, _ := strconv.Atoi(c.Session["NotebookWidth"])
noteListWidth, _ := strconv.Atoi(c.Session["NoteListWidth"])
mdEditorWidth, _ := strconv.Atoi(c.Session["MdEditorWidth"])
LogJ(c.Session)
user := info.User{UserId: bson.ObjectIdHex(userId),
Email: c.Session["Email"],
Logo: c.Session["Logo"],
Username: c.Session["Username"],
UsernameRaw: c.Session["UsernameRaw"],
Theme: c.Session["Theme"],
NotebookWidth: notebookWidth,
NoteListWidth: noteListWidth,
MdEditorWidth: mdEditorWidth,
}
if c.Session["Verified"] == "1" {
user.Verified = true
}
if c.Session["Verified"] == "1" {
user.Verified = true
}
if c.Session["LeftIsMin"] == "1" {
user.LeftIsMin = true
}
return user
if c.Session["LeftIsMin"] == "1" {
user.LeftIsMin = true
}
return user
*/
}
return info.User{}
}
func (c BaseController) GetUserAndBlogUrl() info.UserAndBlogUrl {
if userId, ok := c.Session["UserId"]; ok && userId != "" {
return userService.GetUserAndBlogUrl(userId)
}
return info.UserAndBlogUrl{}
}
// 这里的session都是cookie中的, 与数据库session无关
func (c BaseController) GetSession(key string) string {
v, ok := c.Session[key]
if !ok {
v = ""
}
return v
}
func (c BaseController) SetSession(userInfo info.User) {
if userInfo.UserId.Hex() != "" {
c.Session["UserId"] = userInfo.UserId.Hex()
@@ -82,16 +112,17 @@ func (c BaseController) SetSession(userInfo info.User) {
c.Session["Username"] = userInfo.Username
c.Session["UsernameRaw"] = userInfo.UsernameRaw
c.Session["Theme"] = userInfo.Theme
c.Session["Logo"] = userInfo.Logo
c.Session["NotebookWidth"] = strconv.Itoa(userInfo.NotebookWidth)
c.Session["NoteListWidth"] = strconv.Itoa(userInfo.NoteListWidth)
if userInfo.Verified {
c.Session["Verified"] = "1"
} else {
c.Session["Verified"] = "0"
}
if userInfo.LeftIsMin {
c.Session["LeftIsMin"] = "1"
} else {
@@ -114,8 +145,8 @@ func (c BaseController) UpdateSession(key, value string) {
// 返回json
func (c BaseController) Json(i interface{}) string {
// b, _ := json.MarshalIndent(i, "", " ")
b, _ := json.Marshal(i)
// b, _ := json.MarshalIndent(i, "", " ")
b, _ := json.Marshal(i)
return string(b)
}
@@ -132,9 +163,9 @@ func (c BaseController) GetPage() int {
// 判断是否含有某参数
func (c BaseController) Has(key string) bool {
if _, ok := c.Params.Values[key]; ok {
return true;
return true
}
return false;
return false
}
/*
@@ -144,31 +175,93 @@ func (c Blog) GetPage(page, count int, list interface{}) info.Page {
*/
func (c BaseController) GetTotalPage(page, count int) int {
return int(math.Ceil(float64(count)/float64(page)))
return int(math.Ceil(float64(count) / float64(page)))
}
//-------------
func (c BaseController) E404() revel.Result {
c.RenderArgs["title"] = "404";
c.ViewArgs["title"] = "404"
return c.NotFound("", nil)
}
// 设置本地
func (c BaseController) SetLocale() {
func (c BaseController) SetLocale() string {
locale := string(c.Request.Locale) // zh-CN
// lang := locale
// if strings.Contains(locale, "-") {
// pos := strings.Index(locale, "-")
// lang = locale[0:pos]
// }
// if lang != "zh" && lang != "en" {
// lang = "en"
// }
lang := locale
if strings.Contains(locale, "-") {
pos := strings.Index(locale, "-")
lang = locale[0:pos]
if !i18n.HasLang(locale) {
lang = i18n.GetDefaultLang()
}
if lang != "zh" && lang != "en" {
lang = "en";
}
c.RenderArgs["locale"] = lang;
c.ViewArgs["locale"] = lang
c.ViewArgs["siteUrl"] = configService.GetSiteUrl()
c.ViewArgs["blogUrl"] = configService.GetBlogUrl()
c.ViewArgs["leaUrl"] = configService.GetLeaUrl()
c.ViewArgs["noteUrl"] = configService.GetNoteUrl()
return lang
}
// 设置userInfo
func (c BaseController) SetUserInfo() {
func (c BaseController) SetUserInfo() info.User {
userInfo := c.GetUserInfo()
c.RenderArgs["userInfo"] = userInfo
}
c.ViewArgs["userInfo"] = userInfo
if userInfo.Username == configService.GetAdminUsername() {
c.ViewArgs["isAdmin"] = true
}
return userInfo
}
// life
// 返回解析后的字符串, 只是为了解析模板得到字符串
func (c BaseController) RenderTemplateStr(templatePath string) string {
// Get the Template.
// 返回 GoTemplate{tmpl, loader}
template, err := revel.MainTemplateLoader.Template(templatePath)
if err != nil {
}
tpl := &revel.RenderTemplateResult{
Template: template,
ViewArgs: c.ViewArgs, // 把args给它
}
var buffer bytes.Buffer
tpl.Template.Render(&buffer, c.ViewArgs)
return buffer.String()
}
// json, result
// 为了msg
// msg-v1-v2-v3
func (c BaseController) RenderRe(re info.Re) revel.Result {
oldMsg := re.Msg
if re.Msg != "" {
if strings.Contains(re.Msg, "-") {
msgAndValues := strings.Split(re.Msg, "-")
if len(msgAndValues) == 2 {
re.Msg = c.Message(msgAndValues[0], msgAndValues[1])
} else {
others := msgAndValues[0:]
a := make([]interface{}, len(others))
for i, v := range others {
a[i] = v
}
re.Msg = c.Message(msgAndValues[0], a...)
}
} else {
re.Msg = c.Message(re.Msg)
}
}
if strings.HasPrefix(re.Msg, "???") {
re.Msg = oldMsg
}
return c.RenderJSON(re)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
package controllers
import (
"github.com/revel/revel"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/lea/captcha"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
// "math"
// "os"
// "path"
// "strconv"
"net/http"
)
// 验证码服务
type Captcha struct {
BaseController
}
type Ca string
func (r Ca) Apply(req *revel.Request, resp *revel.Response) {
resp.WriteHeader(http.StatusOK, "image/png")
}
func (c Captcha) Get() revel.Result {
c.Response.ContentType = "image/png"
image, str := captcha.Fetch()
image.WriteTo(c.Response.Out)
sessionId := c.Session["_ID"]
// LogJ(c.Session)
// Log("------")
// Log(str)
// Log(sessionId)
Log("..")
sessionService.SetCaptcha(sessionId, str)
return c.Render()
}

View File

@@ -2,12 +2,16 @@ package controllers
import (
"github.com/revel/revel"
// "encoding/json"
. "github.com/leanote/leanote/app/lea"
// "encoding/json"
"fmt"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/lea/netutil"
"gopkg.in/mgo.v2/bson"
"io/ioutil"
"os"
"strconv"
// "strconv"
"strings"
)
// 首页
@@ -15,98 +19,149 @@ type File struct {
BaseController
}
// 上传图片 editor
func (c File) UploadImage(renderHtml string) revel.Result {
if renderHtml == "" {
renderHtml = "file/image.html"
}
re := c.uploadImage("", "");
c.RenderArgs["fileUrlPath"] = siteUrl + re.Id
c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["resultMsg"] = re.Msg
return c.RenderTemplate(renderHtml)
}
// 上传的是博客logo
// TODO logo不要设置权限, 另外的目录
func (c File) UploadBlogLogo() revel.Result {
return c.UploadImage("file/blog_logo.html");
re := c.uploadImage("blogLogo", "")
c.ViewArgs["fileUrlPath"] = re.Id
c.ViewArgs["resultCode"] = re.Code
c.ViewArgs["resultMsg"] = re.Msg
return c.RenderTemplate("file/blog_logo.html")
}
// 拖拉上传, pasteImage
func (c File) UploadImageJson(renderHtml, from string) revel.Result {
re := c.uploadImage(from, "");
re.Id = siteUrl + re.Id
// re.Id = re.Id
return c.RenderJson(re)
// noteId 是为了判断是否是协作的note, 如果是则需要复制一份到note owner中
func (c File) PasteImage(noteId string) revel.Result {
re := c.uploadImage("pasteImage", "")
if noteId != "" {
userId := c.GetUserId()
note := noteService.GetNoteById(noteId)
if note.UserId != "" {
noteUserId := note.UserId.Hex()
if noteUserId != userId {
// 是否是有权限协作的
if shareService.HasUpdatePerm(noteUserId, userId, noteId) {
// 复制图片之, 图片复制给noteUserId
_, re.Id = fileService.CopyImage(userId, re.Id, noteUserId)
} else {
// 怎么可能在这个笔记下paste图片呢?
// 正常情况下不会
}
}
}
}
return c.RenderJSON(re)
}
// leaui image plugin
// 头像设置
func (c File) UploadAvatar() revel.Result {
re := c.uploadImage("logo", "")
c.ViewArgs["fileUrlPath"] = re.Id
c.ViewArgs["resultCode"] = re.Code
c.ViewArgs["resultMsg"] = re.Msg
if re.Ok {
re.Ok = userService.UpdateAvatar(c.GetUserId(), re.Id)
if re.Ok {
c.UpdateSession("Logo", re.Id)
}
}
return c.RenderJSON(re)
}
// leaui image plugin upload image
func (c File) UploadImageLeaui(albumId string) revel.Result {
re := c.uploadImage("", albumId);
re.Id = siteUrl + re.Id
// re.Id = re.Id
return c.RenderJson(re)
re := c.uploadImage("", albumId)
return c.RenderJSON(re)
}
// 上传图片, 公用方法
// upload image common func
func (c File) uploadImage(from, albumId string) (re info.Re) {
var fileUrlPath = ""
var resultCode = 0 // 1表示正常
var resultMsg = "内部错误" // 错误信息
var fileId = ""
var resultCode = 0 // 1表示正常
var resultMsg = "error" // 错误信息
var Ok = false
defer func() {
re.Id = fileUrlPath
re.Id = fileId // 只是id, 没有其它信息
re.Code = resultCode
re.Msg = resultMsg
re.Ok = Ok
}()
file, handel, err := c.Request.FormFile("file")
if err != nil {
return re
}
defer file.Close()
// 生成上传路径
fileUrlPath = "/upload/" + c.GetUserId() + "/images"
dir := revel.BasePath + "/public/" + fileUrlPath
newGuid := NewGuid()
userId := c.GetUserId()
if from == "logo" || from == "blogLogo" {
fileUrlPath = "public/upload/" + Digest3(userId) + "/" + userId + "/images/logo"
} else {
// fileUrlPath = "files/" + Digest3(userId) + "/" + userId + "/" + Digest2(newGuid) + "/images"
fileUrlPath = "files/" + GetRandomFilePath(userId, newGuid) + "/images"
}
dir := revel.BasePath + "/" + fileUrlPath
err = os.MkdirAll(dir, 0755)
if err != nil {
Log(err)
return re
}
// 生成新的文件名
filename := handel.Filename
var ext string;
var ext string
if from == "pasteImage" {
ext = ".png"; // TODO 可能不是png类型
handel.Filename = c.Message("unTitled")
ext = ".png" // TODO 可能不是png类型
} else {
_, ext = SplitFilename(filename)
if(ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg") {
resultMsg = "不是图片"
if ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg" {
resultMsg = "Please upload image"
return re
}
}
filename = NewGuid() + ext
filename = newGuid + ext
data, err := ioutil.ReadAll(file)
if err != nil {
LogJ(err)
return re
}
var maxFileSize float64
if from == "logo" {
maxFileSize = configService.GetUploadSize("uploadAvatarSize")
} else if from == "blogLogo" {
maxFileSize = configService.GetUploadSize("uploadBlogLogoSize")
} else {
maxFileSize = configService.GetUploadSize("uploadImageSize")
}
if maxFileSize <= 0 {
maxFileSize = 1000
}
// > 2M?
if(len(data) > 5 * 1024 * 1024) {
if float64(len(data)) > maxFileSize*float64(1024*1024) {
resultCode = 0
resultMsg = "图片大于2M"
resultMsg = fmt.Sprintf("The file Size is bigger than %vM", maxFileSize)
return re
}
toPath := dir + "/" + filename;
toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
LogJ(err)
@@ -118,83 +173,105 @@ func (c File) uploadImage(from, albumId string) (re info.Re) {
filesize := GetFilesize(toPathGif)
fileUrlPath += "/" + filename
resultCode = 1
resultMsg = "上传成功!"
resultMsg = "Upload Success!"
// File
fileInfo := info.File{Name: filename,
Title: handel.Filename,
Path: fileUrlPath,
Size: filesize}
Ok = fileService.AddImage(fileInfo, albumId, c.GetUserId())
Path: fileUrlPath,
Size: filesize}
id := bson.NewObjectId()
fileInfo.FileId = id
fileId = id.Hex()
if from == "logo" || from == "blogLogo" {
fileId = fileUrlPath
}
Ok, resultMsg = fileService.AddImage(fileInfo, albumId, c.GetUserId(), from == "" || from == "pasteImage")
resultMsg = c.Message(resultMsg)
fileInfo.Path = "" // 不要返回
re.Item = fileInfo
return re
}
// get all images by userId with page
func (c File) GetImages(albumId, key string, page int) revel.Result {
re := fileService.ListImagesWithPage(c.GetUserId(), albumId, key, page, 12)
return c.RenderJson(re)
return c.RenderJSON(re)
}
func (c File) UpdateImageTitle(fileId, title string) revel.Result {
re := info.NewRe()
re.Ok = fileService.UpdateImageTitle(c.GetUserId(), fileId, title)
return c.RenderJson(re)
return c.RenderJSON(re)
}
func (c File) DeleteImage(fileId string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = fileService.DeleteImage(c.GetUserId(), fileId)
return c.RenderJson(re)
return c.RenderJSON(re)
}
// update image uploader to leaui image,
// scan all user's images and insert into db
func (c File) UpgradeLeauiImage() revel.Result {
//-----------
// 输出image
// 权限判断
func (c File) OutputImage(noteId, fileId string) revel.Result {
path := fileService.GetFile(c.GetUserId(), fileId) // 得到路径
if path == "" {
return c.RenderText("")
}
fn := revel.BasePath + "/" + strings.TrimLeft(path, "/")
file, _ := os.Open(fn)
return c.RenderFile(file, revel.Inline) // revel.Attachment
}
// 协作时复制图片到owner
// 需要计算对方大小
func (c File) CopyImage(userId, fileId, toUserId string) revel.Result {
re := info.NewRe()
if ok, _ := revel.Config.Bool("upgradeLeauiImage"); !ok {
re.Msg = "Not allowed"
return c.RenderJson(re)
re.Ok, re.Id = fileService.CopyImage(userId, fileId, toUserId)
return c.RenderJSON(re)
}
// 复制外网的图片
// 都要好好的计算大小
func (c File) CopyHttpImage(src string) revel.Result {
re := info.NewRe()
// 生成上传路径
newGuid := NewGuid()
userId := c.GetUserId()
// fileUrlPath := "files/" + Digest3(userId) + "/" + userId + "/" + Digest2(newGuid) + "/images"
fileUrlPath := "files/" + GetRandomFilePath(userId, newGuid) + "/images"
dir := revel.BasePath + "/" + fileUrlPath
err := os.MkdirAll(dir, 0755)
if err != nil {
return c.RenderJSON(re)
}
uploadPath := revel.BasePath + "/public/upload";
userIds := ListDir(uploadPath)
if userIds == nil {
re.Msg = "no user"
return c.RenderJson(re)
filesize, filename, _, ok := netutil.WriteUrl(src, dir)
if !ok {
re.Msg = "copy error"
return c.RenderJSON(re)
}
msg := "";
for _, userId := range userIds {
dirPath := uploadPath + "/" + userId + "/images"
images := ListDir(dirPath)
if images == nil {
msg += userId + " no images "
continue;
}
hadImages := fileService.GetAllImageNamesMap(userId)
i := 0
for _, filename := range images {
if _, ok := hadImages[filename]; !ok {
fileUrlPath := "/upload/" + userId + "/images/" + filename
fileInfo := info.File{Name: filename,
Title: filename,
Path: fileUrlPath,
Size: GetFilesize(dirPath + "/" + filename)}
fileService.AddImage(fileInfo, "", userId)
i++
}
}
msg += userId + ": " + strconv.Itoa(len(images)) + " -- " + strconv.Itoa(i) + " images "
}
re.Msg = msg
return c.RenderJson(re)
}
// File
fileInfo := info.File{Name: filename,
Title: filename,
Path: fileUrlPath + "/" + filename,
Size: filesize}
id := bson.NewObjectId()
fileInfo.FileId = id
re.Id = id.Hex()
// re.Item = fileInfo.Path
re.Ok, re.Msg = fileService.AddImage(fileInfo, "", c.GetUserId(), true)
return c.RenderJSON(re)
}

View File

@@ -1,9 +1,9 @@
package controllers
import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea"
)
// 首页
@@ -12,25 +12,33 @@ type Index struct {
BaseController
}
func (c Index) Default() revel.Result {
if configService.HomePageIsAdminsBlog() {
blog := Blog{c.BaseController}
return blog.Index(configService.GetAdminUsername())
}
return c.Index()
}
// leanote展示页, 没有登录的, 或已登录明确要进该页的
func (c Index) Index() revel.Result {
c.SetUserInfo()
c.RenderArgs["title"] = "leanote"
c.RenderArgs["openRegister"] = openRegister
c.ViewArgs["title"] = "leanote"
c.ViewArgs["openRegister"] = configService.GlobalStringConfigs["openRegister"]
c.SetLocale()
return c.RenderTemplate("home/index.html");
return c.RenderTemplate("home/index.html")
}
// 建议
func (c Index) Suggestion(addr, suggestion string) revel.Result {
re := info.NewRe()
re.Ok = suggestionService.AddSuggestion(info.Suggestion{Addr: addr, UserId: c.GetObjectUserId(), Suggestion: suggestion})
// 发给我
go func() {
SendToLeanote("建议", "建议", "UserId: " + c.GetUserId() + " <br /> Suggestions: " + suggestion)
}();
return c.RenderJson(re)
}
emailService.SendEmail("leanote@leanote.com", "建议", "UserId: "+c.GetUserId()+" <br /> Suggestions: "+suggestion)
}()
return c.RenderJSON(re)
}

View File

@@ -1,44 +0,0 @@
package controllers
import (
"github.com/revel/revel"
// "github.com/leanote/leanote/app/info"
)
// 首页
type Mobile struct {
BaseController
}
// leanote展示页, 没有登录的, 或已登录明确要进该页的
func (c Mobile) Index() revel.Result {
c.SetLocale()
userInfo := c.GetUserInfo()
userId := userInfo.UserId.Hex()
// 没有登录
if userId == "" {
return c.RenderTemplate("mobile/login.html")
}
/*
// 已登录了, 那么得到所有信息
notebooks := notebookService.GetNotebooks(userId)
shareNotebooks, sharedUserInfos := shareService.GetShareNotebooks(userId)
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["userInfoJson"] = c.Json(userInfo)
c.RenderArgs["notebooks"] = c.Json(notebooks)
c.RenderArgs["shareNotebooks"] = c.Json(shareNotebooks)
c.RenderArgs["sharedUserInfos"] = c.Json(sharedUserInfos)
c.RenderArgs["tagsJson"] = c.Json(tagService.GetTags(c.GetUserId()))
*/
return c.RenderTemplate("mobile/angular.html");
}
func (c Mobile) Logout() revel.Result {
c.ClearSession()
return c.RenderTemplate("mobile/login.html");
}

View File

@@ -11,6 +11,6 @@ type NoteContentHistory struct {
// 得到list
func (c NoteContentHistory) ListHistories(noteId string) revel.Result {
histories := noteContentHistoryService.ListHistories(noteId, c.GetUserId())
return c.RenderJson(histories)
}
return c.RenderJSON(histories)
}

View File

@@ -2,15 +2,20 @@ package controllers
import (
"github.com/revel/revel"
// "encoding/json"
"gopkg.in/mgo.v2/bson"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/lea/html2image"
// "encoding/json"
"github.com/leanote/leanote/app/info"
// "os"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"os"
"os/exec"
"regexp"
"strings"
"time"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
"fmt"
// "bytes"
// "os"
)
type Note struct {
@@ -20,51 +25,112 @@ type Note struct {
// 笔记首页, 判断是否已登录
// 已登录, 得到用户基本信息(notebook, shareNotebook), 跳转到index.html中
// 否则, 转向登录页面
func (c Note) Index() revel.Result {
func (c Note) Index(noteId, online string) revel.Result {
c.SetLocale()
userInfo := c.GetUserInfo()
userInfo := c.GetUserAndBlogUrl()
userId := userInfo.UserId.Hex()
// 没有登录
if userId == "" {
return c.Redirect("/login")
}
c.RenderArgs["openRegister"] = openRegister
c.ViewArgs["openRegister"] = configService.IsOpenRegister()
// 已登录了, 那么得到所有信息
notebooks := notebookService.GetNotebooks(userId)
shareNotebooks, sharedUserInfos := shareService.GetShareNotebooks(userId)
// 还需要按时间排序(DESC)得到notes
notes := []info.Note{}
noteContent := info.NoteContent{}
if len(notebooks) > 0 {
// _, notes = noteService.ListNotes(c.GetUserId(), "", false, c.GetPage(), pageSize, defaultSortField, false, false);
// 变成最新
_, notes = noteService.ListNotes(c.GetUserId(), "", false, c.GetPage(), 50, defaultSortField, false, false);
if len(notes) > 0 {
noteContent = noteService.GetNoteContent(notes[0].NoteId.Hex(), userId)
// noteId是否存在
// 是否传入了正确的noteId
hasRightNoteId := false
if IsObjectId(noteId) {
note := noteService.GetNoteById(noteId)
if note.NoteId != "" {
var noteOwner = note.UserId.Hex()
noteContent = noteService.GetNoteContent(noteId, noteOwner)
hasRightNoteId = true
c.ViewArgs["curNoteId"] = noteId
c.ViewArgs["curNotebookId"] = note.NotebookId.Hex()
// 打开的是共享的笔记, 那么判断是否是共享给我的默认笔记
if noteOwner != c.GetUserId() {
if shareService.HasReadPerm(noteOwner, c.GetUserId(), noteId) {
// 不要获取notebook下的笔记
// 在前端下发请求
c.ViewArgs["curSharedNoteNotebookId"] = note.NotebookId.Hex()
c.ViewArgs["curSharedUserId"] = noteOwner
// 没有读写权限
} else {
hasRightNoteId = false
}
} else {
_, notes = noteService.ListNotes(c.GetUserId(), note.NotebookId.Hex(), false, c.GetPage(), 50, defaultSortField, false, false)
// 如果指定了某笔记, 则该笔记放在首位
lenNotes := len(notes)
if lenNotes > 1 {
notes2 := make([]info.Note, len(notes))
notes2[0] = note
i := 1
for _, note := range notes {
if note.NoteId.Hex() != noteId {
if i == lenNotes { // 防止越界
break
}
notes2[i] = note
i++
}
}
notes = notes2
}
}
}
// 得到最近的笔记
_, latestNotes := noteService.ListNotes(c.GetUserId(), "", false, c.GetPage(), 50, defaultSortField, false, false)
c.ViewArgs["latestNotes"] = latestNotes
}
// 没有传入笔记
// 那么得到最新笔记
if !hasRightNoteId {
_, notes = noteService.ListNotes(c.GetUserId(), "", false, c.GetPage(), 50, defaultSortField, false, false)
if len(notes) > 0 {
noteContent = noteService.GetNoteContent(notes[0].NoteId.Hex(), userId)
c.ViewArgs["curNoteId"] = notes[0].NoteId.Hex()
}
}
}
// 当然, 还需要得到第一个notes的content
//...
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["userInfoJson"] = c.Json(userInfo)
c.RenderArgs["notebooks"] = c.Json(notebooks)
c.RenderArgs["shareNotebooks"] = c.Json(shareNotebooks)
c.RenderArgs["sharedUserInfos"] = c.Json(sharedUserInfos)
c.RenderArgs["notes"] = c.Json(notes)
c.RenderArgs["noteContentJson"] = c.Json(noteContent)
c.RenderArgs["noteContent"] = noteContent.Content
c.RenderArgs["tagsJson"] = c.Json(tagService.GetTags(c.GetUserId()))
if isDev, _ := revel.Config.Bool("mode.dev"); isDev {
c.ViewArgs["isAdmin"] = configService.GetAdminUsername() == userInfo.Username
c.ViewArgs["userInfo"] = userInfo
c.ViewArgs["notebooks"] = notebooks
c.ViewArgs["shareNotebooks"] = shareNotebooks // note信息在notes列表中
c.ViewArgs["sharedUserInfos"] = sharedUserInfos
c.ViewArgs["notes"] = notes
c.ViewArgs["noteContentJson"] = noteContent
c.ViewArgs["noteContent"] = noteContent.Content
c.ViewArgs["tags"] = tagService.GetTags(c.GetUserId())
c.ViewArgs["globalConfigs"] = configService.GetGlobalConfigForUser()
// return c.RenderTemplate("note/note.html")
if isDev, _ := revel.Config.Bool("mode.dev"); isDev && online == "" {
return c.RenderTemplate("note/note-dev.html")
} else {
return c.RenderTemplate("note/note.html")
@@ -75,138 +141,171 @@ func (c Note) Index() revel.Result {
// 已登录, 得到用户基本信息(notebook, shareNotebook), 跳转到index.html中
// 否则, 转向登录页面
func (c Note) ListNotes(notebookId string) revel.Result {
_, notes := noteService.ListNotes(c.GetUserId(), notebookId, false, c.GetPage(), pageSize, defaultSortField, false, false);
return c.RenderJson(notes)
_, notes := noteService.ListNotes(c.GetUserId(), notebookId, false, c.GetPage(), pageSize, defaultSortField, false, false)
return c.RenderJSON(notes)
}
// 得到trash
func (c Note) ListTrashNotes() revel.Result {
_, notes := noteService.ListNotes(c.GetUserId(), "", true, c.GetPage(), pageSize, defaultSortField, false, false);
return c.RenderJson(notes)
_, notes := noteService.ListNotes(c.GetUserId(), "", true, c.GetPage(), pageSize, defaultSortField, false, false)
return c.RenderJSON(notes)
}
// 得到note和内容
func (c Note) GetNoteAndContent(noteId string) revel.Result {
return c.RenderJson(noteService.GetNoteAndContent(noteId, c.GetUserId()))
return c.RenderJSON(noteService.GetNoteAndContent(noteId, c.GetUserId()))
}
func (c Note) GetNoteAndContentBySrc(src string) revel.Result {
noteId, noteAndContent := noteService.GetNoteAndContentBySrc(src, c.GetUserId())
ret := info.Re{}
if noteId != "" {
ret.Ok = true
ret.Item = noteAndContent
}
return c.RenderJSON(ret)
}
// 得到内容
func (c Note) GetNoteContent(noteId string) revel.Result {
noteContent := noteService.GetNoteContent(noteId, c.GetUserId())
return c.RenderJson(noteContent)
return c.RenderJSON(noteContent)
}
// 更新note或content
// 肯定会传userId(谁的), NoteId
// 会传Title, Content, Tags, 一种或几种
type NoteOrContent struct {
NotebookId string
NoteId string
UserId string
Title string
Desc string
ImgSrc string
Tags []string
Content string
Abstract string
IsNew bool
IsMarkdown bool
FromUserId string // 为共享而新建
IsBlog bool // 是否是blog, 更新note不需要修改, 添加note时才有可能用到, 此时需要判断notebook是否设为Blog
}
// 这里不能用json, 要用post
func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result {
func (c Note) UpdateNoteOrContent(noteOrContent info.NoteOrContent) revel.Result {
// 新添加note
LogJ(noteOrContent)
if noteOrContent.IsNew {
userId := c.GetObjectUserId();
myUserId := userId
userId := c.GetObjectUserId()
// myUserId := userId
// 为共享新建?
if noteOrContent.FromUserId != "" {
userId = bson.ObjectIdHex(noteOrContent.FromUserId)
}
note := info.Note{UserId: userId,
NoteId: bson.ObjectIdHex(noteOrContent.NoteId),
NotebookId: bson.ObjectIdHex(noteOrContent.NotebookId),
Title: noteOrContent.Title,
Tags: noteOrContent.Tags,
Desc: noteOrContent.Desc,
ImgSrc: noteOrContent.ImgSrc,
IsBlog: noteOrContent.IsBlog,
note := info.Note{UserId: userId,
NoteId: bson.ObjectIdHex(noteOrContent.NoteId),
NotebookId: bson.ObjectIdHex(noteOrContent.NotebookId),
Title: noteOrContent.Title,
Src: noteOrContent.Src, // 来源
Tags: strings.Split(noteOrContent.Tags, ","),
Desc: noteOrContent.Desc,
ImgSrc: noteOrContent.ImgSrc,
IsBlog: noteOrContent.IsBlog,
IsMarkdown: noteOrContent.IsMarkdown,
};
noteContent := info.NoteContent{NoteId: note.NoteId,
UserId: userId,
IsBlog: note.IsBlog,
Content: noteOrContent.Content,
Abstract: noteOrContent.Abstract};
note = noteService.AddNoteAndContent(note, noteContent, myUserId)
return c.RenderJson(note)
}
noteContent := info.NoteContent{NoteId: note.NoteId,
UserId: userId,
IsBlog: note.IsBlog,
Content: noteOrContent.Content,
Abstract: noteOrContent.Abstract}
note = noteService.AddNoteAndContentForController(note, noteContent, c.GetUserId())
return c.RenderJSON(note)
}
noteUpdate := bson.M{}
needUpdateNote := false
// Desc前台传来
if c.Has("Desc") {
needUpdateNote = true
noteUpdate["Desc"] = noteOrContent.Desc;
noteUpdate["Desc"] = noteOrContent.Desc
}
if c.Has("ImgSrc") {
needUpdateNote = true
noteUpdate["ImgSrc"] = noteOrContent.ImgSrc;
noteUpdate["ImgSrc"] = noteOrContent.ImgSrc
}
if c.Has("Title") {
needUpdateNote = true
noteUpdate["Title"] = noteOrContent.Title;
noteUpdate["Title"] = noteOrContent.Title
}
if c.Has("Tags[]") {
if c.Has("Tags") {
needUpdateNote = true
noteUpdate["Tags"] = noteOrContent.Tags;
noteUpdate["Tags"] = strings.Split(noteOrContent.Tags, ",")
}
if needUpdateNote {
noteService.UpdateNote(noteOrContent.UserId, c.GetUserId(),
noteOrContent.NoteId, noteUpdate)
// web端不控制
if needUpdateNote {
noteService.UpdateNote(c.GetUserId(),
noteOrContent.NoteId, noteUpdate, -1)
}
//-------------
afterContentUsn := 0
contentOk := false
contentMsg := ""
if c.Has("Content") {
noteService.UpdateNoteContent(noteOrContent.UserId, c.GetUserId(),
noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract)
// noteService.UpdateNoteContent(noteOrContent.UserId, c.GetUserId(),
// noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract)
contentOk, contentMsg, afterContentUsn = noteService.UpdateNoteContent(c.GetUserId(),
noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract,
needUpdateNote, -1, time.Now())
}
return c.RenderJson(true)
Log(afterContentUsn)
Log(contentOk)
Log(contentMsg)
return c.RenderJSON(true)
}
// 删除note/ 删除别人共享给我的笔记
// userId 是note.UserId
func (c Note) DeleteNote(noteId, userId string, isShared bool) revel.Result {
if(!isShared) {
return c.RenderJson(trashService.DeleteNote(noteId, c.GetUserId()));
func (c Note) DeleteNote(noteIds []string, isShared bool) revel.Result {
if !isShared {
for _, noteId := range noteIds {
trashService.DeleteNote(noteId, c.GetUserId())
}
return c.RenderJSON(true)
}
return c.RenderJson(trashService.DeleteSharedNote(noteId, userId, c.GetUserId()));
for _, noteId := range noteIds {
trashService.DeleteSharedNote(noteId, c.GetUserId())
}
return c.RenderJSON(true)
}
// 删除trash
// 删除trash, 已弃用, 用DeleteNote
func (c Note) DeleteTrash(noteId string) revel.Result {
return c.RenderJson(trashService.DeleteTrash(noteId, c.GetUserId()));
return c.RenderJSON(trashService.DeleteTrash(noteId, c.GetUserId()))
}
// 移动note
func (c Note) MoveNote(noteId, notebookId string) revel.Result {
return c.RenderJson(noteService.MoveNote(noteId, notebookId, c.GetUserId()));
func (c Note) MoveNote(noteIds []string, notebookId string) revel.Result {
userId := c.GetUserId()
for _, noteId := range noteIds {
noteService.MoveNote(noteId, notebookId, userId)
}
return c.RenderJSON(true)
}
// 复制note
func (c Note) CopyNote(noteId, notebookId string) revel.Result {
return c.RenderJson(noteService.CopyNote(noteId, notebookId, c.GetUserId()));
func (c Note) CopyNote(noteIds []string, notebookId string) revel.Result {
copyNotes := make([]info.Note, len(noteIds))
userId := c.GetUserId()
for i, noteId := range noteIds {
copyNotes[i] = noteService.CopyNote(noteId, notebookId, userId)
}
re := info.NewRe()
re.Ok = true
re.Item = copyNotes
return c.RenderJSON(re)
}
// 复制别人共享的笔记给我
func (c Note) CopySharedNote(noteId, notebookId, fromUserId string) revel.Result {
return c.RenderJson(noteService.CopySharedNote(noteId, notebookId, fromUserId, c.GetUserId()));
func (c Note) CopySharedNote(noteIds []string, notebookId, fromUserId string) revel.Result {
copyNotes := make([]info.Note, len(noteIds))
userId := c.GetUserId()
for i, noteId := range noteIds {
copyNotes[i] = noteService.CopySharedNote(noteId, notebookId, fromUserId, userId)
}
re := info.NewRe()
re.Ok = true
re.Item = copyNotes
return c.RenderJSON(re)
}
//------------
@@ -214,39 +313,180 @@ func (c Note) CopySharedNote(noteId, notebookId, fromUserId string) revel.Result
// 通过title搜索
func (c Note) SearchNote(key string) revel.Result {
_, blogs := noteService.SearchNote(key, c.GetUserId(), c.GetPage(), pageSize, "UpdatedTime", false, false)
return c.RenderJson(blogs)
return c.RenderJSON(blogs)
}
// 通过tags搜索
func (c Note) SearchNoteByTags(tags []string) revel.Result {
_, blogs := noteService.SearchNoteByTags(tags, c.GetUserId(), c.GetPage(), pageSize, "UpdatedTime", false)
return c.RenderJson(blogs)
return c.RenderJSON(blogs)
}
//-----------------
// html2image
func (c Note) Html2Image(noteId string) revel.Result {
// 生成PDF
func (c Note) ToPdf(noteId, appKey string) revel.Result {
// 虽然传了cookie但是这里还是不能得到userId, 所以还是通过appKey来验证之
appKeyTrue, _ := revel.Config.String("app.secret")
if appKeyTrue != appKey {
return c.RenderText("auth error")
}
note := noteService.GetNoteById(noteId)
if note.NoteId == "" {
return c.RenderText("no note")
}
noteUserId := note.UserId.Hex()
content := noteService.GetNoteContent(noteId, noteUserId)
userInfo := userService.GetUserInfo(noteUserId)
//------------------
// 将content的图片转换为base64
contentStr := content.Content
siteUrlPattern := configService.GetSiteUrl()
if strings.Contains(siteUrlPattern, "https") {
siteUrlPattern = strings.Replace(siteUrlPattern, "https", "https*", 1)
} else {
siteUrlPattern = strings.Replace(siteUrlPattern, "http", "https*", 1)
}
siteUrlPattern = "(?:" + siteUrlPattern + ")*"
regImage, _ := regexp.Compile(`<img .*?(src=('|")` + siteUrlPattern + `/(file/outputImage|api/file/getImage)\?fileId=([a-z0-9A-Z]{24})("|'))`)
findsImage := regImage.FindAllStringSubmatch(contentStr, -1) // 查找所有的
// [<img src="http://leanote.com/api/getImage?fileId=3354672e8d38f411286b000069" alt="" width="692" height="302" data-mce-src="http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069" src="http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069" " file/outputImage 54672e8d38f411286b000069 "]
for _, eachFind := range findsImage {
if len(eachFind) == 6 {
fileId := eachFind[4]
// 得到base64编码文件
fileBase64 := fileService.GetImageBase64(noteUserId, fileId)
if fileBase64 == "" {
continue
}
// 1
// src="http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069"
allFixed := strings.Replace(eachFind[0], eachFind[1], "src=\""+fileBase64+"\"", -1)
contentStr = strings.Replace(contentStr, eachFind[0], allFixed, -1)
}
}
// markdown
if note.IsMarkdown {
// ![enter image description here](url)
regImageMarkdown, _ := regexp.Compile(`!\[.*?\]\(` + siteUrlPattern + `/(file/outputImage|api/file/getImage)\?fileId=([a-z0-9A-Z]{24})\)`)
findsImageMarkdown := regImageMarkdown.FindAllStringSubmatch(contentStr, -1) // 查找所有的
for _, eachFind := range findsImageMarkdown {
if len(eachFind) == 3 {
fileId := eachFind[2]
// 得到base64编码文件
fileBase64 := fileService.GetImageBase64(noteUserId, fileId)
if fileBase64 == "" {
continue
}
// 1
// src="http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069"
allFixed := "![](" + fileBase64 + ")"
contentStr = strings.Replace(contentStr, eachFind[0], allFixed, -1)
}
}
}
if note.Tags != nil && len(note.Tags) > 0 && note.Tags[0] != "" {
} else {
note.Tags = nil
}
c.ViewArgs["blog"] = note
c.ViewArgs["content"] = contentStr
c.ViewArgs["userInfo"] = userInfo
userBlog := blogService.GetUserBlog(noteUserId)
c.ViewArgs["userBlog"] = userBlog
return c.RenderTemplate("file/pdf.html")
}
// 导出成PDF
func (c Note) ExportPdf(noteId string) revel.Result {
re := info.NewRe()
userId := c.GetUserId()
note := noteService.GetNote(noteId, userId)
note := noteService.GetNoteById(noteId)
if note.NoteId == "" {
return c.RenderJson(re)
re.Msg = "No Note"
return c.RenderText("error")
}
content := noteService.GetNoteContent(noteId, userId)
noteUserId := note.UserId.Hex()
// 是否有权限
if noteUserId != userId {
// 是否是有权限协作的
if !note.IsBlog && !shareService.HasReadPerm(noteUserId, userId, noteId) {
re.Msg = "No Perm"
return c.RenderText("No Perm")
}
}
// path 判断是否需要重新生成之
fileUrlPath := "/upload/" + userId + "/images/weibo"
dir := revel.BasePath + "/public/" + fileUrlPath
if !ClearDir(dir) {
return c.RenderJson(re)
guid := NewGuid()
fileUrlPath := "files/export_pdf"
dir := revel.BasePath + "/" + fileUrlPath
if !MkdirAll(dir) {
return c.RenderText("error, no dir")
}
filename := note.NoteId.Hex() + ".png";
filename := guid + ".pdf"
path := dir + "/" + filename
// leanote.com的secret
appKey, _ := revel.Config.String("app.secretLeanote")
if appKey == "" {
appKey, _ = revel.Config.String("app.secret")
}
// 生成之
html2image.ToImage(userId, c.GetUsername(), noteId, note.Title, content.Content, path)
re.Ok = true
re.Id = fileUrlPath + "/" + filename
return c.RenderJson(re)
}
binPath := configService.GetGlobalStringConfig("exportPdfBinPath")
// 默认路径
if binPath == "" {
binPath = "/usr/local/bin/wkhtmltopdf"
}
url := configService.GetSiteUrl() + "/note/toPdf?noteId=" + noteId + "&appKey=" + appKey
// cc := binPath + " --no-stop-slow-scripts --javascript-delay 10000 \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\""
// cc := binPath + " \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\""
// 等待--window-status为done的状态
// http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf_0.10.0_rc2-doc.html
// wkhtmltopdf参数大全
var cc string
if note.IsMarkdown {
cc = binPath + " --lowquality --window-status done \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\""
} else {
cc = binPath + " --lowquality \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\""
}
cmd := exec.Command("/bin/sh", "-c", cc)
_, err := cmd.Output()
if err != nil {
return c.RenderText("export pdf error. " + fmt.Sprintf("%v", err))
}
file, err := os.Open(path)
if err != nil {
return c.RenderText("export pdf error. " + fmt.Sprintf("%v", err))
}
// http://stackoverflow.com/questions/8588818/chrome-pdf-display-duplicate-headers-received-from-the-server
// filenameReturn = strings.Replace(filenameReturn, ",", "-", -1)
filenameReturn := note.Title
filenameReturn = FixFilename(filenameReturn)
if filenameReturn == "" {
filenameReturn = "Untitled.pdf"
} else {
filenameReturn += ".pdf"
}
return c.RenderBinary(file, filenameReturn, revel.Attachment, time.Now()) // revel.Attachment
}
// 设置/取消Blog; 置顶
func (c Note) SetNote2Blog(noteIds []string, isBlog, isTop bool) revel.Result {
for _, noteId := range noteIds {
noteService.ToBlog(c.GetUserId(), noteId, isBlog, isTop)
}
return c.RenderJSON(true)
}

View File

@@ -1,12 +1,12 @@
package controllers
import (
"github.com/revel/revel"
// "encoding/json"
"encoding/json"
"github.com/leanote/leanote/app/info"
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
)
type Notebook struct {
@@ -14,39 +14,67 @@ type Notebook struct {
}
func (c Notebook) Index(notebook info.Notebook, i int, name string) revel.Result {
return c.RenderJson(notebook)
return c.RenderJSON(notebook)
}
// 得到用户的所有笔记本
func (c Notebook) GetNotebooks() revel.Result {
re := notebookService.GetNotebooks(c.GetUserId())
return c.RenderJson(re)
return c.RenderJSON(re)
}
func (c Notebook) DeleteNotebook(notebookId string) revel.Result {
re, msg := notebookService.DeleteNotebook(c.GetUserId(), notebookId)
return c.RenderJson(info.Re{Ok: re, Msg: msg})
return c.RenderJSON(info.Re{Ok: re, Msg: msg})
}
// 添加notebook
func (c Notebook) AddNotebook(notebookId, title string) revel.Result {
notebook := info.Notebook{NotebookId: bson.ObjectIdHex(notebookId),
Title: title,
Seq: -1,
func (c Notebook) AddNotebook(notebookId, title, parentNotebookId string) revel.Result {
notebook := info.Notebook{NotebookId: bson.ObjectIdHex(notebookId),
Title: title,
Seq: -1,
UserId: c.GetObjectUserId()}
re := notebookService.AddNotebook(notebook)
if(re) {
return c.RenderJson(notebook)
if parentNotebookId != "" {
notebook.ParentNotebookId = bson.ObjectIdHex(parentNotebookId)
}
re, notebook := notebookService.AddNotebook(notebook)
if re {
return c.RenderJSON(notebook)
} else {
return c.RenderJson(false)
return c.RenderJSON(false)
}
}
// 修改标题
func (c Notebook) UpdateNotebookTitle(notebookId, title string) revel.Result {
return c.RenderJson(notebookService.UpdateNotebookTitle(notebookId, c.GetUserId(), title))
return c.RenderJSON(notebookService.UpdateNotebookTitle(notebookId, c.GetUserId(), title))
}
// 排序
func (c Notebook) SortNotebooks(notebookId2Seqs map[string]int) revel.Result {
return c.RenderJson(notebookService.SortNotebooks(c.GetUserId(), notebookId2Seqs))
}
// 无用
// func (c Notebook) SortNotebooks(notebookId2Seqs map[string]int) revel.Result {
// return c.RenderJSON(notebookService.SortNotebooks(c.GetUserId(), notebookId2Seqs))
// }
// 调整notebooks, 可能是排序, 可能是移动到其它笔记本下
type DragNotebooksInfo struct {
CurNotebookId string
ParentNotebookId string
Siblings []string
}
// 传过来的data是JSON.stringfy数据
func (c Notebook) DragNotebooks(data string) revel.Result {
info := DragNotebooksInfo{}
json.Unmarshal([]byte(data), &info)
return c.RenderJSON(notebookService.DragNotebooks(c.GetUserId(), info.CurNotebookId, info.ParentNotebookId, info.Siblings))
}
// 设置notebook <-> blog
func (c Notebook) SetNotebook2Blog(notebookId string, isBlog bool) revel.Result {
re := notebookService.ToBlog(c.GetUserId(), notebookId, isBlog)
return c.RenderJSON(re)
}

View File

@@ -1,70 +0,0 @@
package controllers
import (
"code.google.com/p/goauth2/oauth"
"github.com/revel/revel"
"github.com/leanote/leanote/app/lea/netutil"
. "github.com/leanote/leanote/app/lea"
"encoding/json"
"fmt"
)
type Oauth struct {
BaseController
}
var oauthCfg = &oauth.Config{
ClientId: "3790fbf1fc14bc6c5d85",
ClientSecret: "e9dadfe601c7caa6df9b33db3e7539945c60dfa2",
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
RedirectURL: "http://leanote.com/oauth/githubCallback",
Scope: "user",
}
// 用户允许后, github返回到leanote
// 通过code得到token
// https://github.com/login/oauth/authorize?access_type=&approval_prompt=&client_id=3790fbf1fc14bc6c5d85&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Foauth2callback&response_type=code&scope=user&state=
func (c Oauth) GithubCallback(code string) revel.Result {
t := &oauth.Transport{Config: oauthCfg}
// Exchange the received code for a token
tok, err := t.Exchange(code)
token := tok.AccessToken
if err != nil || token == "" {
c.RenderArgs["title"] = "error"
return c.RenderTemplate("oauth/oauth_callback_error.html")
}
// 得到用户信息
profileInfoURL := "https://api.github.com/user"
url := fmt.Sprintf("%s?access_token=%s", profileInfoURL, token)
content, err2 := netutil.GetContent(url)
if err2 != nil {
c.RenderArgs["title"] = "error"
return c.RenderTemplate("oauth/oauth_callback_error.html")
}
// 转成map
profileInfo := map[string]interface{}{}
Log(string(content))
err2 = json.Unmarshal(content, &profileInfo)
if err2 != nil {
c.RenderArgs["title"] = "error"
return c.RenderTemplate("oauth/oauth_callback_error.html")
}
usernameI := profileInfo["login"]
username, _ := usernameI.(string)
userId := username
// 注册
isExists, userInfo := authService.ThirdRegister("github", userId, username)
c.RenderArgs["isExists"] = isExists
c.RenderArgs["userInfo"] = userInfo
// 登录之
c.SetSession(userInfo)
return c.Redirect("/note")
}

View File

@@ -0,0 +1,103 @@
package controllers
import (
"github.com/revel/revel"
// "strings"
// "time"
// "encoding/json"
// "github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
// "github.com/leanote/leanote/app/lea/blog"
// "gopkg.in/mgo.v2/bson"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "math"
// "os"
// "path"
)
type Preview struct {
Blog
}
// 得到要预览的主题绝对路径
func (c Preview) getPreviewThemeAbsolutePath(themeId string) bool {
if themeId != "" {
c.Session["themeId"] = themeId // 存到session中, 下次的url就不能带了, 待优化, 有时会取不到
} else {
themeId = c.Session["themeId"] // 直接从session中获取
}
if themeId == "" {
return false
}
theme := themeService.GetTheme(c.GetUserId(), themeId)
c.ViewArgs["isPreview"] = true
c.ViewArgs["themeId"] = themeId
c.ViewArgs["themeInfoPreview"] = theme.Info
c.ViewArgs["themePath"] = theme.Path
if theme.Path == "" {
return false
}
return true
}
func (c Preview) Index(userIdOrEmail string, themeId string) revel.Result {
if !c.getPreviewThemeAbsolutePath(themeId) {
return c.E404()
}
return c.Blog.Index(c.GetUserId())
// return blog.RenderTemplate("index.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(themeId))
}
func (c Preview) Tag(userIdOrEmail, tag string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Tag(c.GetUserId(), tag)
}
func (c Preview) Tags(userIdOrEmail string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Tags(c.GetUserId())
// if tag == "" {
// return blog.RenderTemplate("tags.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(""))
// }
// return blog.RenderTemplate("tag_posts.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Archives(userIdOrEmail string, notebookId string, year, month int) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Archives(c.GetUserId(), notebookId, year, month)
// return blog.RenderTemplate("archive.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Cate(userIdOrEmail, notebookId string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Cate(userIdOrEmail, notebookId)
// return blog.RenderTemplate("cate.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Post(userIdOrEmail, noteId string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Post(userIdOrEmail, noteId)
// return blog.RenderTemplate("view.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Single(userIdOrEmail, singleId string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Single(userIdOrEmail, singleId)
// return blog.RenderTemplate("single.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Search(userIdOrEmail, keywords string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Search(c.GetUserId(), keywords)
// return blog.RenderTemplate("search.html", c.ViewArgs, c.getPreviewThemeAbsolutePath(""))
}

View File

@@ -2,13 +2,13 @@ package controllers
import (
"github.com/revel/revel"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
"github.com/leanote/leanote/app/info"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
. "github.com/leanote/leanote/app/lea"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
)
type Share struct {
@@ -19,41 +19,41 @@ type Share struct {
func (c Share) AddShareNote(noteId string, emails []string, perm int) revel.Result {
status := make(map[string]info.Re, len(emails))
// 自己不能给自己添加共享
myEmail := c.GetEmail();
myEmail := c.GetEmail()
for _, email := range emails {
if email == "" {
continue;
continue
}
if(myEmail != email) {
ok, msg, userId := shareService.AddShareNote(noteId, perm, c.GetUserId(), email);
if myEmail != email {
ok, msg, userId := shareService.AddShareNote(noteId, perm, c.GetUserId(), email)
status[email] = info.Re{Ok: ok, Msg: msg, Id: userId}
} else {
status[email] = info.Re{Ok: false, Msg: "不能分享给自己"}
}
}
}
return c.RenderJson(status)
return c.RenderJSON(status)
}
// 添加共享notebook
func (c Share) AddShareNotebook(notebookId string, emails []string, perm int) revel.Result {
status := make(map[string]info.Re, len(emails))
// 自己不能给自己添加共享
myEmail := c.GetEmail();
myEmail := c.GetEmail()
for _, email := range emails {
if email == "" {
continue;
continue
}
if(myEmail != email) {
ok, msg, userId := shareService.AddShareNotebook(notebookId, perm, c.GetUserId(), email);
if myEmail != email {
ok, msg, userId := shareService.AddShareNotebook(notebookId, perm, c.GetUserId(), email)
status[email] = info.Re{Ok: ok, Msg: msg, Id: userId}
} else {
status[email] = info.Re{Ok: false, Msg: "不能分享给自己"}
}
}
}
return c.RenderJson(status)
return c.RenderJSON(status)
}
// 得到notes
@@ -62,11 +62,11 @@ func (c Share) ListShareNotes(notebookId, userId string) revel.Result {
// 表示是默认笔记本, 不是某个特定notebook的共享
var notes []info.ShareNoteWithPerm
if notebookId == "" {
notes = shareService.ListShareNotes(c.GetUserId(), userId, c.GetPage(), pageSize, defaultSortField, false);
return c.RenderJson(notes)
notes = shareService.ListShareNotes(c.GetUserId(), userId, c.GetPage(), pageSize, defaultSortField, false)
return c.RenderJSON(notes)
} else {
// 有notebookId的
return c.RenderJson(shareService.ListShareNotesByNotebookId(notebookId, c.GetUserId(), userId, c.GetPage(), pageSize, defaultSortField, false));
return c.RenderJSON(shareService.ListShareNotesByNotebookId(notebookId, c.GetUserId(), userId, c.GetPage(), pageSize, defaultSortField, false))
}
}
@@ -74,7 +74,7 @@ func (c Share) ListShareNotes(notebookId, userId string) revel.Result {
// sharedUserId 是谁的笔记
func (c Share) GetShareNoteContent(noteId, sharedUserId string) revel.Result {
noteContent := shareService.GetShareNoteContent(noteId, c.GetUserId(), sharedUserId)
return c.RenderJson(noteContent)
return c.RenderJSON(noteContent)
}
// 查看note的分享信息
@@ -83,60 +83,110 @@ func (c Share) GetShareNoteContent(noteId, sharedUserId string) revel.Result {
// 还要查看该note的notebookId分享的信息
func (c Share) ListNoteShareUserInfo(noteId string) revel.Result {
note := noteService.GetNote(noteId, c.GetUserId())
noteShareUserInfos := shareService.ListNoteShareUserInfo(noteId, c.GetUserId())
c.RenderArgs["noteOrNotebookShareUserInfos"] = noteShareUserInfos
c.RenderArgs["isNote"] = true
c.RenderArgs["noteOrNotebookId"] = note.NoteId.Hex();
c.RenderArgs["title"] = note.Title
c.ViewArgs["noteOrNotebookShareUserInfos"] = noteShareUserInfos
c.ViewArgs["noteOrNotebookShareGroupInfos"] = shareService.GetNoteShareGroups(noteId, c.GetUserId())
c.ViewArgs["isNote"] = true
c.ViewArgs["noteOrNotebookId"] = note.NoteId.Hex()
c.ViewArgs["title"] = note.Title
return c.RenderTemplate("share/note_notebook_share_user_infos.html")
}
func (c Share) ListNotebookShareUserInfo(notebookId string) revel.Result {
notebook := notebookService.GetNotebook(notebookId, c.GetUserId())
notebookShareUserInfos := shareService.ListNotebookShareUserInfo(notebookId, c.GetUserId())
c.RenderArgs["noteOrNotebookShareUserInfos"] = notebookShareUserInfos
c.RenderArgs["isNote"] = false
c.RenderArgs["noteOrNotebookId"] = notebook.NotebookId.Hex();
c.RenderArgs["title"] = notebook.Title
c.ViewArgs["noteOrNotebookShareUserInfos"] = notebookShareUserInfos
c.ViewArgs["noteOrNotebookShareGroupInfos"] = shareService.GetNotebookShareGroups(notebookId, c.GetUserId())
LogJ(c.ViewArgs["noteOrNotebookShareGroupInfos"])
c.ViewArgs["isNote"] = false
c.ViewArgs["noteOrNotebookId"] = notebook.NotebookId.Hex()
c.ViewArgs["title"] = notebook.Title
return c.RenderTemplate("share/note_notebook_share_user_infos.html")
}
//------------
// 改变share note 权限
func (c Share) UpdateShareNotePerm(noteId string, perm int, toUserId string) revel.Result {
return c.RenderJson(shareService.UpdateShareNotePerm(noteId, perm, c.GetUserId(), toUserId));
return c.RenderJSON(shareService.UpdateShareNotePerm(noteId, perm, c.GetUserId(), toUserId))
}
// 改变share notebook 权限
func (c Share) UpdateShareNotebookPerm(notebookId string, perm int, toUserId string) revel.Result {
return c.RenderJson(shareService.UpdateShareNotebookPerm(notebookId, perm, c.GetUserId(), toUserId));
return c.RenderJSON(shareService.UpdateShareNotebookPerm(notebookId, perm, c.GetUserId(), toUserId))
}
//---------------
// 删除share note
func (c Share) DeleteShareNote(noteId string, toUserId string) revel.Result {
return c.RenderJson(shareService.DeleteShareNote(noteId, c.GetUserId(), toUserId));
return c.RenderJSON(shareService.DeleteShareNote(noteId, c.GetUserId(), toUserId))
}
// 删除share notebook
func (c Share) DeleteShareNotebook(notebookId string, toUserId string) revel.Result {
return c.RenderJson(shareService.DeleteShareNotebook(notebookId, c.GetUserId(), toUserId));
return c.RenderJSON(shareService.DeleteShareNotebook(notebookId, c.GetUserId(), toUserId))
}
// 删除share note, 被共享方删除
func (c Share) DeleteShareNoteBySharedUser(noteId string, fromUserId string) revel.Result {
return c.RenderJson(shareService.DeleteShareNote(noteId, fromUserId, c.GetUserId()));
return c.RenderJSON(shareService.DeleteShareNote(noteId, fromUserId, c.GetUserId()))
}
// 删除share notebook, 被共享方删除
func (c Share) DeleteShareNotebookBySharedUser(notebookId string, fromUserId string) revel.Result {
return c.RenderJson(shareService.DeleteShareNotebook(notebookId, fromUserId, c.GetUserId()));
return c.RenderJSON(shareService.DeleteShareNotebook(notebookId, fromUserId, c.GetUserId()))
}
// 删除fromUserId分享给我的所有note, notebook
func (c Share) DeleteUserShareNoteAndNotebook(fromUserId string) revel.Result {
return c.RenderJson(shareService.DeleteUserShareNoteAndNotebook(fromUserId, c.GetUserId()));
}
return c.RenderJSON(shareService.DeleteUserShareNoteAndNotebook(fromUserId, c.GetUserId()))
}
//-------------
// 用户组
// 将笔记分享给分组
func (c Share) AddShareNoteGroup(noteId, groupId string, perm int) revel.Result {
re := info.NewRe()
re.Ok = shareService.AddShareNoteGroup(c.GetUserId(), noteId, groupId, perm)
return c.RenderJSON(re)
}
// 删除
func (c Share) DeleteShareNoteGroup(noteId, groupId string) revel.Result {
re := info.NewRe()
re.Ok = shareService.DeleteShareNoteGroup(c.GetUserId(), noteId, groupId)
return c.RenderJSON(re)
}
// 更新, 也是一样, 先删后加
func (c Share) UpdateShareNoteGroupPerm(noteId, groupId string, perm int) revel.Result {
return c.AddShareNoteGroup(noteId, groupId, perm)
}
//------
// 将笔记分享给分组
func (c Share) AddShareNotebookGroup(notebookId, groupId string, perm int) revel.Result {
re := info.NewRe()
re.Ok = shareService.AddShareNotebookGroup(c.GetUserId(), notebookId, groupId, perm)
return c.RenderJSON(re)
}
// 删除
func (c Share) DeleteShareNotebookGroup(notebookId, groupId string) revel.Result {
re := info.NewRe()
re.Ok = shareService.DeleteShareNotebookGroup(c.GetUserId(), notebookId, groupId)
return c.RenderJSON(re)
}
// 更新, 也是一样, 先删后加
func (c Share) UpdateShareNotebookGroupPerm(notebookId, groupId string, perm int) revel.Result {
return c.AddShareNotebookGroup(notebookId, groupId, perm)
}

View File

@@ -0,0 +1,30 @@
package controllers
import (
"github.com/revel/revel"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
// "os/exec"
)
type Tag struct {
BaseController
}
// 更新Tag
func (c Tag) UpdateTag(tag string) revel.Result {
ret := info.NewRe()
ret.Ok = true
ret.Item = tagService.AddOrUpdateTag(c.GetUserId(), tag)
return c.RenderJSON(ret)
}
// 删除标签
func (c Tag) DeleteTag(tag string) revel.Result {
ret := info.Re{}
ret.Ok = true
ret.Item = tagService.DeleteTag(c.GetUserId(), tag)
return c.RenderJSON(ret)
}

View File

@@ -2,16 +2,16 @@ package controllers
import (
"github.com/revel/revel"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
. "github.com/leanote/leanote/app/lea"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
"github.com/leanote/leanote/app/info"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
"fmt"
// "math"
// "os"
// "path"
. "github.com/leanote/leanote/app/lea"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
// "math"
// "os"
// "path"
"strconv"
)
@@ -19,70 +19,69 @@ type User struct {
BaseController
}
func (c User) Account(tab int) revel.Result {
userInfo := c.GetUserInfo()
c.ViewArgs["userInfo"] = userInfo
c.ViewArgs["tab"] = tab
c.SetLocale()
return c.RenderTemplate("user/account.html")
}
// 修改用户名, 需要重置session
func (c User) UpdateUsername(username string) revel.Result {
re := info.NewRe();
// 判断是否满足最基本的, 4位, 不含特殊字符, 大小写无关. email大小写无关
if len(username) < 4 {
re.Ok = false
re.Msg = "至少4位"
return c.RenderJson(re);
re := info.NewRe()
if c.GetUserId() == configService.GetGlobalStringConfig("demoUserId") {
re.Msg = "cannotUpdateDemo"
return c.RenderRe(re)
}
if !IsUsername(username) {
re.Ok = false
re.Msg = "不能包含特殊字符"
return c.RenderJson(re);
if re.Ok, re.Msg = Vd("username", username); !re.Ok {
return c.RenderRe(re)
}
re.Ok, re.Msg = userService.UpdateUsername(c.GetUserId(), username)
if(re.Ok) {
if re.Ok {
c.UpdateSession("Username", username)
}
return c.RenderJson(re);
return c.RenderRe(re)
}
// 修改密码
func (c User) UpdatePwd(oldPwd, pwd string) revel.Result {
re := info.NewRe();
if oldPwd == "" {
re.Msg = "旧密码错误"
return c.RenderJson(re);
re := info.NewRe()
if c.GetUserId() == configService.GetGlobalStringConfig("demoUserId") {
re.Msg = "cannotUpdateDemo"
return c.RenderRe(re)
}
re.Ok, re.Msg = IsGoodPwd(pwd)
if !re.Ok {
return c.RenderJson(re);
if re.Ok, re.Msg = Vd("password", oldPwd); !re.Ok {
return c.RenderRe(re)
}
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re)
}
re.Ok, re.Msg = userService.UpdatePwd(c.GetUserId(), oldPwd, pwd)
return c.RenderJson(re);
return c.RenderRe(re)
}
// 更新主题
func (c User) UpdateTheme(theme string) revel.Result {
re := info.NewRe();
re := info.NewRe()
re.Ok = userService.UpdateTheme(c.GetUserId(), theme)
if re.Ok {
c.UpdateSession("Theme", theme)
}
return c.RenderJson(re);
return c.RenderJSON(re)
}
// 发送邀请链接
func (c User) SendRegisterEmail(content, toEmail string) revel.Result {
re := info.NewRe()
if content == "" || !IsEmail(toEmail) {
return c.RenderJson(re);
return c.RenderJSON(re)
}
// 发送邮件
var userInfo = c.GetUserInfo();
url := "http://leanote.com/register?from=" + userInfo.Username
body := fmt.Sprintf("点击链接注册leanote: <a href='%v'>%v</a>. ", url, url);
body = content + "<br />" + body
re.Ok = SendEmail(toEmail, userInfo.Username + "邀请您注册leanote", "邀请注册", body)
return c.RenderJson(re);
re.Ok = emailService.SendInviteEmail(c.GetUserInfo(), toEmail, content)
return c.RenderJSON(re)
}
//---------------------------
@@ -90,107 +89,72 @@ func (c User) SendRegisterEmail(content, toEmail string) revel.Result {
// 重新发送激活邮件
func (c User) ReSendActiveEmail() revel.Result {
re := info.NewRe()
re.Ok = userService.RegisterSendActiveEmail(c.GetUserId(), c.GetEmail())
return c.RenderJson(re)
}
// 修改Email发送激活邮箱
func (c User) UpdateEmailSendActiveEmail(email string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = userService.UpdateEmailSendActiveEmail(c.GetUserId(), email)
return c.RenderJson(re)
re.Ok = emailService.RegisterSendActiveEmail(c.GetUserInfo(), c.GetEmail())
return c.RenderJSON(re)
}
// 通过点击链接
// 修改邮箱
func (c User) UpdateEmail(token string) revel.Result {
c.SetUserInfo();
c.SetUserInfo()
ok, msg, email := userService.UpdateEmail(token)
c.RenderArgs["title"] = "验证邮箱"
c.RenderArgs["ok"] = ok
c.RenderArgs["msg"] = msg
c.RenderArgs["email"] = email
c.ViewArgs["title"] = "验证邮箱"
c.ViewArgs["ok"] = ok
c.ViewArgs["msg"] = msg
c.ViewArgs["email"] = email
// 修改session
if ok {
c.UpdateSession("Email", email)
}
return c.RenderTemplate("user/update_email.html")
}
// 注册后激活邮箱
func (c User) ActiveEmail(token string) revel.Result {
c.SetUserInfo();
c.SetUserInfo()
ok, msg, email := userService.ActiveEmail(token)
// 需要修改session
if ok {
c.UpdateSession("Verified", "1");
c.UpdateSession("Verified", "1")
}
c.RenderArgs["title"] = "验证邮箱"
c.RenderArgs["ok"] = ok
c.RenderArgs["msg"] = msg
c.RenderArgs["email"] = email
return c.RenderTemplate("user/active_email.html")
}
//------------
// 第三方账号添加leanote账号
func (c User) AddAccount(email, pwd string) revel.Result {
re := info.NewRe()
if email == "" {
re.Msg = "请输入邮箱"
return c.RenderJson(re)
} else if !IsEmail(email) {
re.Msg = "请输入正确的邮箱"
return c.RenderJson(re)
}
// 密码
if pwd == "" {
re.Msg = "请输入密码"
return c.RenderJson(re)
} else if len(pwd) < 6 {
re.Msg = "密码长度至少6位"
return c.RenderJson(re)
}
re.Ok, re.Msg = userService.ThirdAddUser(c.GetUserId(), email, pwd)
if re.Ok {
c.UpdateSession("Email", email);
}
return c.RenderJson(re)
c.ViewArgs["title"] = "验证邮箱"
c.ViewArgs["ok"] = ok
c.ViewArgs["msg"] = msg
c.ViewArgs["email"] = email
return c.RenderTemplate("user/active_email.html")
}
//-----------------
// 用户偏爱
func (c User) UpdateColumnWidth(notebookWidth, noteListWidth int) revel.Result {
func (c User) UpdateColumnWidth(notebookWidth, noteListWidth, mdEditorWidth int) revel.Result {
re := info.NewRe()
re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth)
re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth, mdEditorWidth)
if re.Ok {
c.UpdateSession("NotebookWidth", strconv.Itoa(notebookWidth));
c.UpdateSession("NoteListWidth", strconv.Itoa(noteListWidth));
c.UpdateSession("NotebookWidth", strconv.Itoa(notebookWidth))
c.UpdateSession("NoteListWidth", strconv.Itoa(noteListWidth))
c.UpdateSession("MdEditorWidth", strconv.Itoa(mdEditorWidth))
LogJ(c.Session)
}
return c.RenderJson(re)
return c.RenderJSON(re)
}
func (c User) UpdateLeftIsMin(leftIsMin bool) revel.Result {
re := info.NewRe()
re.Ok = userService.UpdateLeftIsMin(c.GetUserId(), leftIsMin)
if re.Ok {
if leftIsMin {
c.UpdateSession("LeftIsMin", "1");
c.UpdateSession("LeftIsMin", "1")
} else {
c.UpdateSession("LeftIsMin", "0");
c.UpdateSession("LeftIsMin", "0")
}
}
return c.RenderJson(re)
}
return c.RenderJSON(re)
}

View File

@@ -0,0 +1,59 @@
package admin
import (
// "github.com/revel/revel"
// "gopkg.in/mgo.v2/bson"
// "encoding/json"
"github.com/leanote/leanote/app/controllers"
. "github.com/leanote/leanote/app/lea"
// "io/ioutil"
// "fmt"
// "math"
// "strconv"
"strings"
)
// 公用Controller, 其它Controller继承它
type AdminBaseController struct {
controllers.BaseController // 不能用*BaseController
}
// 得到sorterField 和 isAsc
// okSorter = ['email', 'username']
func (c AdminBaseController) getSorter(sorterField string, isAsc bool, okSorter []string) (string, bool) {
sorter := ""
c.Params.Bind(&sorter, "sorter")
if sorter == "" {
return sorterField, isAsc
}
// sorter形式 email-up, email-down
s2 := strings.Split(sorter, "-")
if len(s2) != 2 {
return sorterField, isAsc
}
// 必须是可用的sorter
if okSorter != nil && len(okSorter) > 0 {
if !InArray(okSorter, s2[0]) {
return sorterField, isAsc
}
}
sorterField = strings.Title(s2[0])
if s2[1] == "up" {
isAsc = true
} else {
isAsc = false
}
c.ViewArgs["sorter"] = sorter
return sorterField, isAsc
}
func (c AdminBaseController) updateConfig(keys []string) {
userId := c.GetUserId()
for _, key := range keys {
v := c.Params.Values.Get(key)
configService.UpdateGlobalStringConfig(userId, key, v)
}
}

View File

@@ -0,0 +1,30 @@
package admin
import (
"github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
)
// admin 首页
type AdminBlog struct {
AdminBaseController
}
// admin 主页
func (c AdminBlog) Index(sorter, keywords string) revel.Result {
pageNumber := c.GetPage()
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"title", "userId", "isRecommed", "createdTime"})
pageInfo, blogs := blogService.ListAllBlogs("", "", keywords, false, pageNumber, userPageSize, sorterField, isAsc)
c.ViewArgs["pageInfo"] = pageInfo
c.ViewArgs["blogs"] = blogs
c.ViewArgs["keywords"] = keywords
return c.RenderTemplate("admin/blog/list.html")
}
func (c AdminBlog) SetRecommend(noteId string, recommend bool) revel.Result {
re := info.NewRe()
re.Ok = blogService.SetRecommend(noteId, recommend)
return c.RenderJSON(re)
}

View File

@@ -0,0 +1,39 @@
package admin
import (
"github.com/revel/revel"
)
// admin 首页
type Admin struct {
AdminBaseController
}
// admin 主页
func (c Admin) Index() revel.Result {
c.SetUserInfo()
c.ViewArgs["title"] = "leanote"
c.SetLocale()
c.ViewArgs["countUser"] = userService.CountUser()
c.ViewArgs["countNote"] = noteService.CountNote("")
c.ViewArgs["countBlog"] = noteService.CountBlog("")
return c.RenderTemplate("admin/index.html")
}
// 模板
func (c Admin) T(t string) revel.Result {
c.ViewArgs["str"] = configService.GlobalStringConfigs
c.ViewArgs["arr"] = configService.GlobalArrayConfigs
c.ViewArgs["map"] = configService.GlobalMapConfigs
c.ViewArgs["arrMap"] = configService.GlobalArrMapConfigs
c.ViewArgs["version"] = configService.GetVersion()
return c.RenderTemplate("admin/" + t + ".html")
}
func (c Admin) GetView(view string) revel.Result {
return c.RenderTemplate("admin/" + view)
}

View File

@@ -0,0 +1,114 @@
package admin
import (
"archive/tar"
"compress/gzip"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"io"
"os"
"time"
)
// 数据管理, 备份和恢复
type AdminData struct {
AdminBaseController
}
func (c AdminData) Index() revel.Result {
backups := configService.GetGlobalArrMapConfig("backups")
// 逆序之
backups2 := make([]map[string]string, len(backups))
j := 0
for i := len(backups) - 1; i >= 0; i-- {
backups2[j] = backups[i]
j++
}
c.ViewArgs["backups"] = backups2
return c.RenderTemplate("admin/data/index.html")
}
func (c AdminData) Backup() revel.Result {
re := info.NewRe()
re.Ok, re.Msg = configService.Backup("")
return c.RenderJSON(re)
}
// 还原
func (c AdminData) Restore(createdTime string) revel.Result {
re := info.Re{}
re.Ok, re.Msg = configService.Restore(createdTime)
return c.RenderJSON(re)
}
func (c AdminData) Delete(createdTime string) revel.Result {
re := info.Re{}
re.Ok, re.Msg = configService.DeleteBackup(createdTime)
return c.RenderJSON(re)
}
func (c AdminData) UpdateRemark(createdTime, remark string) revel.Result {
re := info.Re{}
re.Ok, re.Msg = configService.UpdateBackupRemark(createdTime, remark)
return c.RenderJSON(re)
}
func (c AdminData) Download(createdTime string) revel.Result {
backup, ok := configService.GetBackup(createdTime)
if !ok {
return c.RenderText("")
}
dbname, _ := revel.Config.String("db.dbname")
path := backup["path"] + "/" + dbname
allFiles := ListDir(path)
filename := "backup_" + dbname + "_" + backup["createdTime"] + ".tar.gz"
// file write
fw, err := os.Create(revel.BasePath + "/files/" + filename)
if err != nil {
return c.RenderText("")
}
// defer fw.Close() // 不需要关闭, 还要读取给用户下载
// gzip write
gw := gzip.NewWriter(fw)
defer gw.Close()
// tar write
tw := tar.NewWriter(gw)
defer tw.Close()
// 遍历文件列表
for _, file := range allFiles {
fn := path + "/" + file
fr, err := os.Open(fn)
fileInfo, _ := fr.Stat()
if err != nil {
return c.RenderText("")
}
defer fr.Close()
// 信息头
h := new(tar.Header)
h.Name = file
h.Size = fileInfo.Size()
h.Mode = int64(fileInfo.Mode())
h.ModTime = fileInfo.ModTime()
// 写信息头
err = tw.WriteHeader(h)
if err != nil {
panic(err)
}
// 写文件
_, err = io.Copy(tw, fr)
if err != nil {
panic(err)
}
} // for
return c.RenderBinary(fw, filename, revel.Attachment, time.Now()) // revel.Attachm
}

View File

@@ -0,0 +1,236 @@
package admin
import (
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"strconv"
"strings"
)
// admin 首页
type AdminEmail struct {
AdminBaseController
}
// email配置
func (c AdminEmail) Email() revel.Result {
return nil
}
// blog标签设置
func (c AdminEmail) Blog() revel.Result {
recommendTags := configService.GetGlobalArrayConfig("recommendTags")
newTags := configService.GetGlobalArrayConfig("newTags")
c.ViewArgs["recommendTags"] = strings.Join(recommendTags, ",")
c.ViewArgs["newTags"] = strings.Join(newTags, ",")
return c.RenderTemplate("admin/setting/blog.html")
}
func (c AdminEmail) DoBlogTag(recommendTags, newTags string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "recommendTags", strings.Split(recommendTags, ","))
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "newTags", strings.Split(newTags, ","))
return c.RenderJSON(re)
}
// demo
// blog标签设置
func (c AdminEmail) Demo() revel.Result {
c.ViewArgs["demoUsername"] = configService.GetGlobalStringConfig("demoUsername")
c.ViewArgs["demoPassword"] = configService.GetGlobalStringConfig("demoPassword")
return c.RenderTemplate("admin/setting/demo.html")
}
func (c AdminEmail) DoDemo(demoUsername, demoPassword string) revel.Result {
re := info.NewRe()
userInfo, err := authService.Login(demoUsername, demoPassword)
if err != nil {
return c.RenderJSON(info.Re{Ok: false})
}
if userInfo.UserId == "" {
re.Msg = "The User is Not Exists"
return c.RenderJSON(re)
}
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUserId", userInfo.UserId.Hex())
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUsername", demoUsername)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoPassword", demoPassword)
return c.RenderJSON(re)
}
// ToImage
// 长微博的bin路径phantomJs
func (c AdminEmail) ToImage() revel.Result {
c.ViewArgs["toImageBinPath"] = configService.GetGlobalStringConfig("toImageBinPath")
return c.RenderTemplate("admin/setting/toImage.html")
}
func (c AdminEmail) DoToImage(toImageBinPath string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "toImageBinPath", toImageBinPath)
return c.RenderJSON(re)
}
func (c AdminEmail) Set(emailHost, emailPort, emailUsername, emailPassword, emailSSL string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailHost", emailHost)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailPort", emailPort)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailUsername", emailUsername)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailPassword", emailPassword)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailSSL", emailSSL)
return c.RenderJSON(re)
}
func (c AdminEmail) Template() revel.Result {
re := info.NewRe()
keys := []string{"emailTemplateHeader", "emailTemplateFooter",
"emailTemplateRegisterSubject",
"emailTemplateRegister",
"emailTemplateFindPasswordSubject",
"emailTemplateFindPassword",
"emailTemplateUpdateEmailSubject",
"emailTemplateUpdateEmail",
"emailTemplateInviteSubject",
"emailTemplateInvite",
"emailTemplateCommentSubject",
"emailTemplateComment",
}
userId := c.GetUserId()
for _, key := range keys {
v := c.Params.Values.Get(key)
if v != "" {
ok, msg := emailService.ValidTpl(v)
if !ok {
re.Ok = false
re.Msg = "Error key: " + key + "<br />" + msg
return c.RenderJSON(re)
} else {
configService.UpdateGlobalStringConfig(userId, key, v)
}
}
}
re.Ok = true
return c.RenderJSON(re)
}
// 发送Email
func (c AdminEmail) SendEmailToEmails(sendEmails, latestEmailSubject, latestEmailBody string, verified, saveAsOldEmail bool) revel.Result {
re := info.NewRe()
c.updateConfig([]string{"sendEmails", "latestEmailSubject", "latestEmailBody"})
if latestEmailSubject == "" || latestEmailBody == "" {
re.Msg = "subject or body is blank"
return c.RenderJSON(re)
}
if saveAsOldEmail {
oldEmails := configService.GetGlobalMapConfig("oldEmails")
oldEmails[latestEmailSubject] = latestEmailBody
configService.UpdateGlobalMapConfig(c.GetUserId(), "oldEmails", oldEmails)
}
sendEmails = strings.Replace(sendEmails, "\r", "", -1)
emails := strings.Split(sendEmails, "\n")
re.Ok, re.Msg = emailService.SendEmailToEmails(emails, latestEmailSubject, latestEmailBody)
return c.RenderJSON(re)
}
// 发送Email
func (c AdminEmail) SendToUsers2(emails, latestEmailSubject, latestEmailBody string, verified, saveAsOldEmail bool) revel.Result {
re := info.NewRe()
c.updateConfig([]string{"sendEmails", "latestEmailSubject", "latestEmailBody"})
if latestEmailSubject == "" || latestEmailBody == "" {
re.Msg = "subject or body is blank"
return c.RenderJSON(re)
}
if saveAsOldEmail {
oldEmails := configService.GetGlobalMapConfig("oldEmails")
oldEmails[latestEmailSubject] = latestEmailBody
configService.UpdateGlobalMapConfig(c.GetUserId(), "oldEmails", oldEmails)
}
emails = strings.Replace(emails, "\r", "", -1)
emailsArr := strings.Split(emails, "\n")
users := userService.ListUserInfosByEmails(emailsArr)
LogJ(emailsArr)
re.Ok, re.Msg = emailService.SendEmailToUsers(users, latestEmailSubject, latestEmailBody)
return c.RenderJSON(re)
}
// send Email dialog
func (c AdminEmail) SendEmailDialog(emails string) revel.Result {
emailsArr := strings.Split(emails, ",")
emailsNl := strings.Join(emailsArr, "\n")
c.ViewArgs["emailsNl"] = emailsNl
c.ViewArgs["str"] = configService.GlobalStringConfigs
c.ViewArgs["map"] = configService.GlobalMapConfigs
return c.RenderTemplate("admin/email/emailDialog.html")
}
func (c AdminEmail) SendToUsers(userFilterEmail, userFilterWhiteList, userFilterBlackList, latestEmailSubject, latestEmailBody string, verified, saveAsOldEmail bool) revel.Result {
re := info.NewRe()
c.updateConfig([]string{"userFilterEmail", "userFilterWhiteList", "userFilterBlackList", "latestEmailSubject", "latestEmailBody"})
if latestEmailSubject == "" || latestEmailBody == "" {
re.Msg = "subject or body is blank"
return c.RenderJSON(re)
}
if saveAsOldEmail {
oldEmails := configService.GetGlobalMapConfig("oldEmails")
oldEmails[latestEmailSubject] = latestEmailBody
configService.UpdateGlobalMapConfig(c.GetUserId(), "oldEmails", oldEmails)
}
users := userService.GetAllUserByFilter(userFilterEmail, userFilterWhiteList, userFilterBlackList, verified)
if users == nil || len(users) == 0 {
re.Ok = false
re.Msg = "no users"
return c.RenderJSON(re)
}
re.Ok, re.Msg = emailService.SendEmailToUsers(users, latestEmailSubject, latestEmailBody)
if !re.Ok {
return c.RenderJSON(re)
}
re.Ok = true
re.Msg = "users:" + strconv.Itoa(len(users))
return c.RenderJSON(re)
}
// 删除emails
func (c AdminEmail) DeleteEmails(ids string) revel.Result {
re := info.NewRe()
re.Ok = emailService.DeleteEmails(strings.Split(ids, ","))
return c.RenderJSON(re)
}
func (c AdminEmail) List(sorter, keywords string) revel.Result {
pageNumber := c.GetPage()
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"email", "ok", "subject", "createdTime"})
pageInfo, emails := emailService.ListEmailLogs(pageNumber, userPageSize, sorterField, isAsc, keywords)
c.ViewArgs["pageInfo"] = pageInfo
c.ViewArgs["emails"] = emails
c.ViewArgs["keywords"] = keywords
return c.RenderTemplate("admin/email/list.html")
}

View File

@@ -0,0 +1,142 @@
package admin
import (
"github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea"
"fmt"
"github.com/leanote/leanote/app/info"
"strings"
)
// admin 首页
type AdminSetting struct {
AdminBaseController
}
// email配置
func (c AdminSetting) Email() revel.Result {
return nil
}
// blog标签设置
func (c AdminSetting) Blog() revel.Result {
recommendTags := configService.GetGlobalArrayConfig("recommendTags")
newTags := configService.GetGlobalArrayConfig("newTags")
c.ViewArgs["recommendTags"] = strings.Join(recommendTags, ",")
c.ViewArgs["newTags"] = strings.Join(newTags, ",")
return c.RenderTemplate("admin/setting/blog.html")
}
func (c AdminSetting) DoBlogTag(recommendTags, newTags string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "recommendTags", strings.Split(recommendTags, ","))
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "newTags", strings.Split(newTags, ","))
return c.RenderJSON(re)
}
// 共享设置
func (c AdminSetting) ShareNote(registerSharedUserId string,
registerSharedNotebookPerms, registerSharedNotePerms []int,
registerSharedNotebookIds, registerSharedNoteIds, registerCopyNoteIds []string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = configService.UpdateShareNoteConfig(registerSharedUserId, registerSharedNotebookPerms, registerSharedNotePerms, registerSharedNotebookIds, registerSharedNoteIds, registerCopyNoteIds)
return c.RenderJSON(re)
}
// demo
// blog标签设置
func (c AdminSetting) Demo() revel.Result {
c.ViewArgs["demoUsername"] = configService.GetGlobalStringConfig("demoUsername")
c.ViewArgs["demoPassword"] = configService.GetGlobalStringConfig("demoPassword")
return c.RenderTemplate("admin/setting/demo.html")
}
func (c AdminSetting) DoDemo(demoUsername, demoPassword string) revel.Result {
re := info.NewRe()
userInfo, err := authService.Login(demoUsername, demoPassword)
if err != nil {
fmt.Println(err)
return c.RenderJSON(info.Re{Ok: false})
}
if userInfo.UserId == "" {
re.Msg = "The User is Not Exists"
return c.RenderJSON(re)
}
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUserId", userInfo.UserId.Hex())
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUsername", demoUsername)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoPassword", demoPassword)
return c.RenderJSON(re)
}
func (c AdminSetting) ExportPdf(path string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "exportPdfBinPath", path)
return c.RenderJSON(re)
}
func (c AdminSetting) DoSiteUrl(siteUrl string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "siteUrl", siteUrl)
return c.RenderJSON(re)
}
// SubDomain
func (c AdminSetting) SubDomain() revel.Result {
c.ViewArgs["str"] = configService.GlobalStringConfigs
c.ViewArgs["arr"] = configService.GlobalArrayConfigs
c.ViewArgs["noteSubDomain"] = configService.GetGlobalStringConfig("noteSubDomain")
c.ViewArgs["blogSubDomain"] = configService.GetGlobalStringConfig("blogSubDomain")
c.ViewArgs["leaSubDomain"] = configService.GetGlobalStringConfig("leaSubDomain")
return c.RenderTemplate("admin/setting/subDomain.html")
}
func (c AdminSetting) DoSubDomain(noteSubDomain, blogSubDomain, leaSubDomain, blackSubDomains, allowCustomDomain, blackCustomDomains string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "noteSubDomain", noteSubDomain)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "blogSubDomain", blogSubDomain)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "leaSubDomain", leaSubDomain)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "allowCustomDomain", allowCustomDomain)
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "blackSubDomains", strings.Split(blackSubDomains, ","))
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "blackCustomDomains", strings.Split(blackCustomDomains, ","))
return c.RenderJSON(re)
}
func (c AdminSetting) OpenRegister(openRegister string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "openRegister", openRegister)
return c.RenderJSON(re)
}
func (c AdminSetting) HomePage(homePage string) revel.Result {
re := info.NewRe()
if homePage == "0" {
homePage = ""
}
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "homePage", homePage)
return c.RenderJSON(re)
}
func (c AdminSetting) Mongodb(mongodumpPath, mongorestorePath string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "mongodumpPath", mongodumpPath)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "mongorestorePath", mongorestorePath)
return c.RenderJSON(re)
}
func (c AdminSetting) UploadSize(uploadImageSize, uploadAvatarSize, uploadBlogLogoSize, uploadAttachSize float64) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadImageSize", fmt.Sprintf("%v", uploadImageSize))
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadAvatarSize", fmt.Sprintf("%v", uploadAvatarSize))
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadBlogLogoSize", fmt.Sprintf("%v", uploadBlogLogoSize))
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadAttachSize", fmt.Sprintf("%v", uploadAttachSize))
return c.RenderJSON(re)
}

View File

@@ -0,0 +1,30 @@
package admin
import (
"github.com/revel/revel"
// "encoding/json"
"github.com/leanote/leanote/app/info"
// "io/ioutil"
)
// Upgrade controller
type AdminUpgrade struct {
AdminBaseController
}
func (c AdminUpgrade) UpgradeBlog() revel.Result {
upgradeService.UpgradeBlog()
return nil
}
func (c AdminUpgrade) UpgradeBetaToBeta2() revel.Result {
re := info.NewRe()
re.Ok, re.Msg = upgradeService.UpgradeBetaToBeta2(c.GetUserId())
return c.RenderJSON(re)
}
func (c AdminUpgrade) UpgradeBeta3ToBeta4() revel.Result {
re := info.NewRe()
re.Ok, re.Msg = upgradeService.Api(c.GetUserId())
return c.RenderJSON(re)
}

View File

@@ -0,0 +1,67 @@
package admin
import (
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
// "time"
"github.com/leanote/leanote/app/info"
)
// admin 首页
type AdminUser struct {
AdminBaseController
}
// admin 主页
var userPageSize = 10
func (c AdminUser) Index(sorter, keywords string, pageSize int) revel.Result {
pageNumber := c.GetPage()
if pageSize == 0 {
pageSize = userPageSize
}
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"email", "username", "verified", "createdTime", "accountType"})
pageInfo, users := userService.ListUsers(pageNumber, pageSize, sorterField, isAsc, keywords)
c.ViewArgs["pageInfo"] = pageInfo
c.ViewArgs["users"] = users
c.ViewArgs["keywords"] = keywords
return c.RenderTemplate("admin/user/list.html")
}
func (c AdminUser) Add() revel.Result {
return c.RenderTemplate("admin/user/add.html")
}
// 添加
func (c AdminUser) Register(email, pwd string) revel.Result {
re := info.NewRe()
if re.Ok, re.Msg = Vd("email", email); !re.Ok {
return c.RenderRe(re)
}
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re)
}
// 注册
re.Ok, re.Msg = authService.Register(email, pwd, "")
return c.RenderRe(re)
}
// 修改帐户
func (c AdminUser) ResetPwd(userId string) revel.Result {
userInfo := userService.GetUserInfo(userId)
c.ViewArgs["userInfo"] = userInfo
return c.RenderTemplate("admin/user/reset_pwd.html")
}
func (c AdminUser) DoResetPwd(userId, pwd string) revel.Result {
re := info.NewRe()
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re)
}
re.Ok, re.Msg = userService.ResetPwd(c.GetUserId(), userId, pwd)
return c.RenderRe(re)
}

View File

@@ -0,0 +1,132 @@
package admin
import (
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/service"
// . "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
// "strings"
)
var userService *service.UserService
var noteService *service.NoteService
var trashService *service.TrashService
var notebookService *service.NotebookService
var noteContentHistoryService *service.NoteContentHistoryService
var authService *service.AuthService
var shareService *service.ShareService
var blogService *service.BlogService
var tagService *service.TagService
var pwdService *service.PwdService
var tokenService *service.TokenService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
var noteImageService *service.NoteImageService
var fileService *service.FileService
var attachService *service.AttachService
var configService *service.ConfigService
var emailService *service.EmailService
var upgradeService *service.UpgradeService
// 拦截器
// 不需要拦截的url
// Index 除了Note之外都不需要
var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": true,
"Login": true,
"DoLogin": true,
"Logout": true,
"Register": true,
"DoRegister": true,
"FindPasswword": true,
"DoFindPassword": true,
"FindPassword2": true,
"FindPasswordUpdate": true,
"Suggestion": true,
},
"Blog": map[string]bool{"Index": true,
"View": true,
"AboutMe": true,
"SearchBlog": true,
},
// 用户的激活与修改邮箱都不需要登录, 通过链接地址
"User": map[string]bool{"UpdateEmail": true,
"ActiveEmail": true,
},
"Oauth": map[string]bool{"GithubCallback": true},
"File": map[string]bool{"OutputImage": true, "OutputFile": true},
"Attach": map[string]bool{"Download": true, "DownloadAll": true},
}
func needValidate(controller, method string) bool {
// 在里面
if v, ok := commonUrl[controller]; ok {
// 在commonUrl里
if _, ok2 := v[method]; ok2 {
return false
}
return true
} else {
// controller不在这里的, 肯定要验证
return true
}
}
func AuthInterceptor(c *revel.Controller) revel.Result {
// 全部变成首字大写
/*
var controller = strings.Title(c.Name)
var method = strings.Title(c.MethodName)
// 是否需要验证?
if !needValidate(controller, method) {
return nil
}
*/
// 验证是否已登录
// 必须是管理员
if username, ok := c.Session["Username"]; ok && username == configService.GetAdminUsername() {
return nil // 已登录
}
// 没有登录, 判断是否是ajax操作
if c.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
re := info.NewRe()
re.Msg = "NOTLOGIN"
return c.RenderJSON(re)
}
return c.Redirect("/login")
}
// 最外层init.go调用
// 获取service, 单例
func InitService() {
notebookService = service.NotebookS
noteService = service.NoteS
noteContentHistoryService = service.NoteContentHistoryS
trashService = service.TrashS
shareService = service.ShareS
userService = service.UserS
tagService = service.TagS
blogService = service.BlogS
tokenService = service.TokenS
noteImageService = service.NoteImageS
fileService = service.FileS
albumService = service.AlbumS
attachService = service.AttachS
pwdService = service.PwdS
suggestionService = service.SuggestionS
authService = service.AuthS
configService = service.ConfigS
emailService = service.EmailS
upgradeService = service.UpgradeS
}
func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Admin{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminSetting{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminUser{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminBlog{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminEmail{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminUpgrade{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminData{})
}

View File

@@ -0,0 +1,465 @@
# API 列表
## 前言
### api url
所有api的url前面带/api/, 如:
`/api/user/info?userId=xxxx&token=xxxx`
除了/auth/login, /auth/register外其它的都需要另外带参数token=xxxx
### 文件目录结构
* 所有API的Controller都在app/api文件夹下
* 文件命名: Api功能Controller.go, 如ApiUserController.go
* 结构体命名为 Api功能, 如ApiUser
* API公用Controller: ApiBaseController
* init.go 注入service和定义拦截器
### 流程
用户登录后返回一个token, 以后所有的请求都携带该token.
在init.go中的拦截器会得到token并调用sessionService判断是否登录了
## 返回值结构
* 全部返回JSON, JSON, 除二进制文件(图片, 附件外), 如果返回其它非JSON格式的值, 肯定是出错了
* 错误信息全部返回 {Ok: false, Msg: "相应的错误信息"}
* 正确信息返回分两种:
1. 一些操作型的api, 比如updateUsername, updateLogo之类的, 成功后返回 {Ok: true, Msg:""}
2. 一些获取型api, 如getNote, 全部返回真实的返回数据, 如返回笔记:
```
{
"NoteId": "54bdc7e305fcd13ea3000000",
"NotebookId": "54bdc65599c37b0da9000003",
"UserId": "54bdc65599c37b0da9000002",
"Title": "笔记标题",
"Desc": "",
"Tags": null,
"Abstract": "",
"Content": "",
"IsMarkdown": false,
"IsBlog": false,
"IsTrash": true,
"IsDeleted": false,
"Usn": 15,
"Files": [],
"CreatedTime": "2015-01-20T11:13:41.34+08:00",
"UpdatedTime": "2015-01-20T11:13:41.34+08:00",
"PublicTime": "0001-01-01T00:00:00Z"
}
```
* 时间类型全是返回如 "2015-01-20T11:13:41.34+08:00" 的格式, (因为golang的time转成Json就是这样, 历史原因)
-----------------
## Auth 登录与注册
### /auth/login 登录
```
参数: email, pwd
Method: GET
返回:
错误: {"Ok":false, "Msg":"用户名或密码有误"}
正确: 比如:
{
"Ok":true,
"Token":"5500830738f41138e90003232",
"UserId":"52d26b4e99c37b609a000001",
"Email":"leanote@leanote.com",
"Username":"leanote"
}
```
登录成功后将使用token作为之后的请求
### /auth/logout 注销
```
参数: token
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: {Ok: true, Msg: ""}
```
### /auth/register 注册
```
参数: email, pwd
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: {Ok: true, Msg: ""}
```
## User 用户
### /user/info 获取用户信息
```
参数: userId
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: type.User
```
### /user/updateUsername 修改用户名
```
参数: username(新用户名)
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: {Ok: true, Msg: ""}
```
### /user/updatePwd 修改密码
```
参数: oldPwd(旧密码), pwd(新密码)
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: {Ok: true, Msg: ""}
```
### /user/updateLogo 修改头像
```
参数: file(文件)
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: {Ok: true, Msg: ""}
```
### /user/getSyncState 获取最新同步状态
```
参数: 无
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: {LastSyncUsn: 3232, LastSyncTime: "上次同步时间"(暂时无用)}
```
-----
## Notebook 笔记本
### /notebook/getSyncNotebooks 得到需要同步的笔记本
```
参数: afterUsn(int, 在此usn后的笔记本是需要同步的), maxEntry(int, 最大要同步的量)
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: [type.Notebook] 数组
```
### /notebook/getNotebooks 得到所有笔记本
```
无参数
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: [type.Notebook] 数组
```
### /notebook/addNotebook 添加笔记本
```
参数: title(string), parentNotebookId(string, 父notebookId, 可空), seq(int) 排列
Method: POST
返回:
错误: {Ok: false, Msg:""}
成功: type.Notebook
```
### /notebook/updateNotebook 修改笔记本
```
参数: notebookId, title, parentNotebookId, seq(int), usn(int)
Method: POST
返回:
错误: {Ok: false, msg: ""} msg == "conflict" 表示冲突
成功: type.Notebook
```
### /notebook/deleteNotebook 删除笔记本
```
参数: notebookId, usn(int)
Method: GET
返回:
错误: {Ok: false, msg: ""} msg == "conflict" 表示冲突
成功: {Ok: true}
```
----
## Note 笔记
### /note/getSyncNotes 获取需要同步的笔记
```
参数: afterUsn(int, 在此usn后的笔记是需要同步的), maxEntry(int, 最大要同步的量)
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: [type.Note] 数组, 笔记不包含Abstract和Content
```
### /note/getNotes 获得某笔记本下的笔记(无内容)
```
参数: notebookId
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: [type.Note] 数组, 笔记不包含Abstract和Content
```
### /note/getNoteAndContent 获得笔记与内容
```
参数: noteId
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: type.Note
```
### /note/getNoteContent 获得笔记内容
```
参数: noteId
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: type.NoteContent
```
### /note/addNote 添加笔记
```
参数: (注意首字大写)
NotebookId string 必传
Title string 必传
Tags []string 可选
Content string 必传
Abstract string 可选, 当是markdown笔记时必须传
IsMarkdown bool 可选
Files []type.NoteFiles 数组 可选
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: type.Note, 不包含Abstract, Content
```
** 关于笔记中的图片/附件**
客户端应该添加一个"图片/附件表"来存元数据, 图片应该要缓存到本地, 附件可在需要的时候再调用相应api获取.
Content中的数据, 图片,附件在Leanote的链接是, 所以, 不管你在本地的笔记中是以什么形式来保存图片,附件的链接的,请addNote和updateNote时务必将链接修改成leanote服务器上的链接.
http://leanote.com/api/file/getImage?fileId=xx
单个附件:
http://leanote.com/api/file/getAttach?fileId=xx
所有附件:
http://leanote.com/api/file/getAllAttachs?noteId=xxx
**注意:**
addNote时必须要把Files, 和相关的图片/附件一起传到服务器中
其中Files(文件的元数据)和其它字段以POST方式传出, 而真正数据则以http的multipart传入, 每个文件的name为"FileDatas[LocalFileId]"
图片在笔记内的链接必须为: http://leanote.com/api/file/getImage?fileId=LocalFileId或FileId
附件如果插入到了笔记内容内, 其链接必须为: http://leanote.com/api/file/getAttach?fileId=LocalFileId或FileId
其中, fileId为文件在本地的LocalFileId或服务器上的FileId
服务器端会生成FileId传给Client. Client在本地必须要建立LocalFileId与FileId的关联.
如果笔记内容传了, 且笔记内有图片, 则必须要传Files 文件元数据, 因为Server端还要对内容内的图片, 附件链接进行修改, 可能你传过去的是LocalFileId, 服务器端会将LocalFileId替换成FileId存到数据库中.
同样适用于 updateNote
http://leanote.com 不绝对, 因为用户可以自建服务, 所以在开发时需要可配置
### /note/updateNote 更新笔记
当更新了笔记某个属性时, 只要传某个属性就行, 其它不用传, 比如把笔记拉入了trash, 那么就传IsTrash: true
```
参数: (注意首字大写)
NoteId string 必传
Usn int 必传
NotebookId string 可选
Title string 可选
Tags []string 可选
Content string 可选
Abstract string 可选, 当是markdown笔记时必须传
IsMarkdown bool 可选
IsTrash bool 是否是trash 可选
Files []type.NoteFiles 数组 可选
Method: POST
返回:
错误: {Ok: false, msg: ''} msg == 'conflict' 表示冲突
成功: type.Note, 不包含Abstract和Content
```
### /note/deleteTrash 彻底删除笔记
```
参数: noteId, usn
Method: GET
返回:
错误: {Ok: false, msg: ''} msg == 'conflict' 表示冲突
成功: type.UpdateRet
```
-------
## Tag 标签
### /tag/getSyncTags 获取需要同步的标签
```
参数: afterUsn(int, 在此usn后的标签是需要同步的), maxEntry(int, 最大要同步的量)
Method: GET
返回:
错误: {Ok: false, Msg: ""}
成功: [type.Tag] 数组
```
### /tag/addTag 添加标签
```
参数: tag(string)
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: type.Tag
```
### /tag/deleteTag 删除标签
```
参数: tag(string)
Method: POST
返回:
错误: {Ok: false, Msg: ""}
成功: type.UpdateRet
```
### File 文件(获取图片, 附件)
### /file/getImage 获取图片
```
参数: fileId
Method: GET
返回:
错误: 非二进制文件数据
成功: 二进制文件
```
### /file/getAttach 获取附件
```
参数: fileId
Method: GET
返回:
错误: 非二进制文件数据
成功: 二进制文件
```
### /file/getAllAttachs 获取所有附件
```
参数: noteId
Method: GET
返回:
错误: 非二进制文件数据
成功: 二进制文件
```
--------
## 数据类型
### type.User 用户信息
```
User {
UserId string
Username string
Email string
Verified bool
Logo string
}
```
### type.Notebook 笔记本
```
Notebook {
NotebookId
UserId
ParentNotebookId // 上级
Seq int // 排序
Title string
IsBlog bool
IsDeleted bool
CreatedTime time.Time
UpdatedTime time.Time
// 更新序号
Usn int // UpdateSequenceNum
}
```
### type.Note 笔记
```
Note {
NoteId string
NotebookId string
UserId string
Title string
Tags []string
Content string
IsMarkdown bool
IsBlog bool
IsTrash bool
Files []NoteFile // 图片, 附件
CreatedTime time.Time
UpdatedTime time.Time
PublicTime time.Time
// 更新序号
Usn int
}
```
### type.NoteContent 笔记内容
```
NoteContent {
NoteId string
UserId string
Content string
}
```
### type.NoteFile 笔记文件(图片,附件)
```
NoteFile {
FileId string // 服务器端Id
LocalFileId string // 客户端Id
Type string // images/png, doc, xls, 根据fileName确定
Title string
HasBody bool // 传过来的值是否要更新内容, 如果有true, 则必须传文件
IsAttach bool // 是否是附件, 不是附件就是图片
}
```
### type.Tag 标签
```
Tag {
TagId string
UserId string
Tag string
CreatedTime
UpdatedTime
IsDeleted bool // 删除位
// 更新序号
Usn
}
```
### type.UpdateRe 更新后返回的值, 包含Usn
```
ReUpdate {
Ok bool
Msg string
// 更新序号
Usn int
}
```

View File

@@ -0,0 +1,47 @@
# API设计
By life (life@leanote.com)
## api url
所有api的url前面带/api/, 如:
`/api/user/info?userId=xxxx&token=xxxx`
## 文件目录结构
* 所有API的Controller都在app/api文件夹下
* 文件命名: Api功能Controller.go, 如ApiUserController.go
* 结构体命名为 Api功能, 如ApiUser
* API公用Controller: ApiBaseController
* init.go 注入service和定义拦截器
## 流程
用户登录后返回一个token, 以后所有的请求都携带该token.
在init.go中的拦截器会得到token并调用sessionService判断是否登录了
## 返回值结构
* 全部返回JSON, JSON, 除二进制文件(图片, 附件外), 如果返回其它非JSON格式的值, 肯定是出错了
* 错误信息全部返回 {Ok: false, Msg: ""}
* 正确信息全部返回真实的返回数据, 如返回笔记:
```
{
"NoteId": "54bdc7e305fcd13ea3000000",
"NotebookId": "54bdc65599c37b0da9000003",
"UserId": "54bdc65599c37b0da9000002",
"Title": "asdfads",
"Desc": "",
"Tags": null,
"Abstract": "",
"Content": "",
"IsMarkdown": false,
"IsBlog": false,
"IsTrash": true,
"IsDeleted": false,
"Usn": 15,
"Files": [],
"CreatedTime": "2015-01-20T11:13:41.34+08:00",
"UpdatedTime": "2015-01-20T11:13:41.34+08:00",
"PublicTime": "0001-01-01T00:00:00Z"
}
```
* 时间类型全是返回如 "2015-01-20T11:13:41.34+08:00" 的格式, (因为golang的time转成Json就是这样, 历史原因)

View File

@@ -0,0 +1,69 @@
package api
import (
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
// "strconv"
)
// 用户登录后生成一个token, 将这个token保存到session中
// 以后每次的请求必须带这个token, 并从session中获取userId
// 用户登录/注销/找回密码
type ApiAuth struct {
ApiBaseContrller
}
// 登录
// [ok]
// 成功返回 {Ok: true, Item: token }
// 失败返回 {Ok: false, Msg: ""}
func (c ApiAuth) Login(email, pwd string) revel.Result {
var msg = ""
userInfo, err := authService.Login(email, pwd)
if err == nil {
token := bson.NewObjectId().Hex()
sessionService.SetUserId(token, userInfo.UserId.Hex())
return c.RenderJSON(info.AuthOk{Ok: true, Token: token, UserId: userInfo.UserId, Email: userInfo.Email, Username: userInfo.Username})
} else {
// 登录错误, 则错误次数++
msg = "wrongUsernameOrPassword"
}
return c.RenderJSON(info.ApiRe{Ok: false, Msg: c.Message(msg)})
}
// 注销
// [Ok]
func (c ApiAuth) Logout() revel.Result {
token := c.getToken()
sessionService.Clear(token)
re := info.NewApiRe()
re.Ok = true
return c.RenderJSON(re)
}
// 注册
// [Ok]
// 成功后并不返回用户ID, 需要用户重新登录
func (c ApiAuth) Register(email, pwd string) revel.Result {
re := info.NewApiRe()
if !configService.IsOpenRegister() {
re.Msg = "notOpenRegister" // 未开放注册
return c.RenderJSON(re)
}
if re.Ok, re.Msg = Vd("email", email); !re.Ok {
return c.RenderJSON(re)
}
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderJSON(re)
}
// 注册
re.Ok, re.Msg = authService.Register(email, pwd, "")
return c.RenderJSON(re)
}

View File

@@ -0,0 +1,189 @@
package api
import (
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
// "encoding/json"
"github.com/leanote/leanote/app/controllers"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"os"
// "fmt"
"io/ioutil"
// "fmt"
// "math"
// "strconv"
"strings"
)
// 公用Controller, 其它Controller继承它
type ApiBaseContrller struct {
controllers.BaseController // 不能用*BaseController
}
// 得到token, 这个token是在AuthInterceptor设到Session中的
func (c ApiBaseContrller) getToken() string {
return c.Session["_token"]
}
// userId
// _userId是在AuthInterceptor设置的
func (c ApiBaseContrller) getUserId() string {
return c.Session["_userId"]
}
// 得到用户信息
func (c ApiBaseContrller) getUserInfo() info.User {
userId := c.Session["_userId"]
if userId == "" {
return info.User{}
}
return userService.GetUserInfo(userId)
}
// 上传附件
func (c ApiBaseContrller) uploadAttach(name string, noteId string) (ok bool, msg string, id string) {
userId := c.getUserId()
// 判断是否有权限为笔记添加附件
// 如果笔记还没有添加是不是会有问题
/*
if !shareService.HasUpdateNotePerm(noteId, userId) {
return
}
*/
file, handel, err := c.Request.FormFile(name)
if err != nil {
return
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return
}
// > 5M?
maxFileSize := configService.GetUploadSize("uploadAttachSize")
if maxFileSize <= 0 {
maxFileSize = 1000
}
if float64(len(data)) > maxFileSize*float64(1024*1024) {
msg = "fileIsTooLarge"
return
}
// 生成上传路径
newGuid := NewGuid()
// filePath := "files/" + Digest3(userId) + "/" + userId + "/" + Digest2(newGuid) + "/attachs"
filePath := "files/" + GetRandomFilePath(userId, newGuid) + "/attachs"
dir := revel.BasePath + "/" + filePath
err = os.MkdirAll(dir, 0755)
if err != nil {
return
}
// 生成新的文件名
filename := handel.Filename
_, ext := SplitFilename(filename) // .doc
filename = newGuid + ext
toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
return
}
// add File to db
fileType := ""
if ext != "" {
fileType = strings.ToLower(ext[1:])
}
filesize := GetFilesize(toPath)
fileInfo := info.Attach{AttachId: bson.NewObjectId(),
Name: filename,
Title: handel.Filename,
NoteId: bson.ObjectIdHex(noteId),
UploadUserId: bson.ObjectIdHex(userId),
Path: filePath + "/" + filename,
Type: fileType,
Size: filesize}
ok, msg = attachService.AddAttach(fileInfo, true)
if !ok {
return
}
id = fileInfo.AttachId.Hex()
return
}
// 上传图片
func (c ApiBaseContrller) upload(name string, noteId string, isAttach bool) (ok bool, msg string, id string) {
if isAttach {
return c.uploadAttach(name, noteId)
}
file, handel, err := c.Request.FormFile(name)
if err != nil {
return
}
defer file.Close()
newGuid := NewGuid()
// 生成上传路径
userId := c.getUserId()
// fileUrlPath := "files/" + Digest3(userId) + "/" + userId + "/" + Digest2(newGuid) + "/images"
fileUrlPath := "files/" + GetRandomFilePath(userId, newGuid) + "/images"
dir := revel.BasePath + "/" + fileUrlPath
err = os.MkdirAll(dir, 0755)
if err != nil {
return
}
// 生成新的文件名
filename := handel.Filename
_, ext := SplitFilename(filename)
// if ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg" {
// msg = "notImage"
// return
// }
filename = newGuid + ext
data, err := ioutil.ReadAll(file)
if err != nil {
return
}
maxFileSize := configService.GetUploadSize("uploadImageSize")
if maxFileSize <= 0 {
maxFileSize = 1000
}
// > 2M?
if float64(len(data)) > maxFileSize*float64(1024*1024) {
msg = "fileIsTooLarge"
return
}
toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
return
}
// 改变成gif图片
_, toPathGif := TransToGif(toPath, 0, true)
filename = GetFilename(toPathGif)
filesize := GetFilesize(toPathGif)
fileUrlPath += "/" + filename
// File
fileInfo := info.File{FileId: bson.NewObjectId(),
Name: filename,
Title: handel.Filename,
Path: fileUrlPath,
Size: filesize}
ok, msg = fileService.AddImage(fileInfo, "", c.getUserId(), true)
if ok {
id = fileInfo.FileId.Hex()
}
return
}

View File

@@ -0,0 +1,163 @@
package api
import (
"github.com/revel/revel"
// "encoding/json"
// . "github.com/leanote/leanote/app/lea"
// "gopkg.in/mgo.v2/bson"
// "github.com/leanote/leanote/app/lea/netutil"
// "github.com/leanote/leanote/app/info"
// "io/ioutil"
"os"
// "strconv"
"archive/tar"
"compress/gzip"
"io"
"strings"
"time"
)
// 文件操作, 图片, 头像上传, 输出
type ApiFile struct {
ApiBaseContrller
}
/*
// 协作时复制图片到owner
func (c ApiFile) CopyImage(userId, fileId, toUserId string) revel.Result {
re := info.NewRe()
re.Ok, re.Id = fileService.CopyImage(userId, fileId, toUserId)
return c.RenderJSON(re)
}
// get all images by userId with page
func (c ApiFile) GetImages(albumId, key string, page int) revel.Result {
imagesPage := fileService.ListImagesWithPage(c.getUserId(), albumId, key, page, 12)
re := info.NewRe()
re.Ok = true
re.Item = imagesPage
return c.RenderJSON(re)
}
func (c ApiFile) UpdateImageTitle(fileId, title string) revel.Result {
re := info.NewRe()
re.Ok = fileService.UpdateImageTitle(c.getUserId(), fileId, title)
return c.RenderJSON(re)
}
func (c ApiFile) DeleteImage(fileId string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = fileService.DeleteImage(c.getUserId(), fileId)
return c.RenderJSON(re)
}
*/
//-----------
// 输出image
// [OK]
func (c ApiFile) GetImage(fileId string) revel.Result {
path := fileService.GetFile(c.getUserId(), fileId) // 得到路径
if path == "" {
return c.RenderText("")
}
fn := revel.BasePath + "/" + strings.TrimLeft(path, "/")
file, _ := os.Open(fn)
return c.RenderFile(file, revel.Inline) // revel.Attachment
}
// 下载附件
// [OK]
func (c ApiFile) GetAttach(fileId string) revel.Result {
attach := attachService.GetAttach(fileId, c.getUserId()) // 得到路径
path := attach.Path
if path == "" {
return c.RenderText("No Such File")
}
fn := revel.BasePath + "/" + strings.TrimLeft(path, "/")
file, _ := os.Open(fn)
return c.RenderBinary(file, attach.Title, revel.Attachment, time.Now()) // revel.Attachment
}
// 下载所有附件
// [OK]
func (c ApiFile) GetAllAttachs(noteId string) revel.Result {
note := noteService.GetNoteById(noteId)
if note.NoteId == "" {
return c.RenderText("")
}
// 得到文件列表
attachs := attachService.ListAttachs(noteId, c.getUserId())
if attachs == nil || len(attachs) == 0 {
return c.RenderText("")
}
/*
dir := revel.BasePath + "/files/tmp"
err := os.MkdirAll(dir, 0755)
if err != nil {
return c.RenderText("")
}
*/
filename := note.Title + ".tar.gz"
if note.Title == "" {
filename = "all.tar.gz"
}
// file write
fw, err := os.Create(revel.BasePath + "/files/" + filename)
if err != nil {
return c.RenderText("")
}
// defer fw.Close() // 不需要关闭, 还要读取给用户下载
// gzip write
gw := gzip.NewWriter(fw)
defer gw.Close()
// tar write
tw := tar.NewWriter(gw)
defer tw.Close()
// 遍历文件列表
for _, attach := range attachs {
fn := revel.BasePath + "/" + strings.TrimLeft(attach.Path, "/")
fr, err := os.Open(fn)
fileInfo, _ := fr.Stat()
if err != nil {
return c.RenderText("")
}
defer fr.Close()
// 信息头
h := new(tar.Header)
h.Name = attach.Title
h.Size = fileInfo.Size()
h.Mode = int64(fileInfo.Mode())
h.ModTime = fileInfo.ModTime()
// 写信息头
err = tw.WriteHeader(h)
if err != nil {
panic(err)
}
// 写文件
_, err = io.Copy(tw, fr)
if err != nil {
panic(err)
}
} // for
// tw.Close()
// gw.Close()
// fw.Close()
// file, _ := os.Open(dir + "/" + filename)
// fw.Seek(0, 0)
return c.RenderBinary(fw, filename, revel.Attachment, time.Now()) // revel.Attachment
}

View File

@@ -0,0 +1,642 @@
package api
import (
"github.com/revel/revel"
// "encoding/json"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"os"
"os/exec"
// "strings"
"time"
"regexp"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
// "bytes"
// "os"
)
// 笔记API
type ApiNote struct {
ApiBaseContrller
}
// 获取同步的笔记
// > afterUsn的笔记
// 无Desc, Abstract, 有Files
/*
{
"NoteId": "55195fa199c37b79be000005",
"NotebookId": "55195fa199c37b79be000002",
"UserId": "55195fa199c37b79be000001",
"Title": "Leanote语法Leanote语法Leanote语法Leanote语法Leanote语法",
"Desc": "",
"Tags": null,
"Abstract": "",
"Content": "",
"IsMarkdown": true,
"IsBlog": false,
"IsTrash": false,
"Usn": 5,
"Files": [],
"CreatedTime": "2015-03-30T22:37:21.695+08:00",
"UpdatedTime": "2015-03-30T22:37:21.724+08:00",
"PublicTime": "2015-03-30T22:37:21.695+08:00"
}
*/
func (c ApiNote) GetSyncNotes(afterUsn, maxEntry int) revel.Result {
if maxEntry == 0 {
maxEntry = 100
}
notes := noteService.GetSyncNotes(c.getUserId(), afterUsn, maxEntry)
return c.RenderJSON(notes)
}
// 得到笔记本下的笔记
// [OK]
func (c ApiNote) GetNotes(notebookId string) revel.Result {
if notebookId != "" && !bson.IsObjectIdHex(notebookId) {
re := info.NewApiRe()
re.Msg = "notebookIdInvalid"
return c.RenderJSON(re)
}
_, notes := noteService.ListNotes(c.getUserId(), notebookId, false, c.GetPage(), pageSize, defaultSortField, false, false)
return c.RenderJSON(noteService.ToApiNotes(notes))
}
// 得到trash
// [OK]
func (c ApiNote) GetTrashNotes() revel.Result {
_, notes := noteService.ListNotes(c.getUserId(), "", true, c.GetPage(), pageSize, defaultSortField, false, false)
return c.RenderJSON(noteService.ToApiNotes(notes))
}
// get Note
// [OK]
/*
{
"NoteId": "550c0bee2ec82a2eb5000000",
"NotebookId": "54a1676399c37b1c77000004",
"UserId": "54a1676399c37b1c77000002",
"Title": "asdfadsf--=",
"Desc": "",
"Tags": [
],
"Abstract": "",
"Content": "",
"IsMarkdown": false,
"IsBlog": false,
"IsTrash": false,
"Usn": 8,
"Files": [
{
"FileId": "551975d599c37b970f000000",
"LocalFileId": "",
"Type": "",
"Title": "",
"HasBody": false,
"IsAttach": false
},
{
"FileId": "551975de99c37b970f000001",
"LocalFileId": "",
"Type": "doc",
"Title": "李铁-print-en.doc",
"HasBody": false,
"IsAttach": true
},
{
"FileId": "551975de99c37b970f000002",
"LocalFileId": "",
"Type": "doc",
"Title": "李铁-print.doc",
"HasBody": false,
"IsAttach": true
}
],
"CreatedTime": "2015-03-20T20:00:52.463+08:00",
"UpdatedTime": "2015-03-31T00:12:44.967+08:00",
"PublicTime": "2015-03-20T20:00:52.463+08:00"
}
*/
func (c ApiNote) GetNote(noteId string) revel.Result {
if !bson.IsObjectIdHex(noteId) {
re := info.NewApiRe()
re.Msg = "noteIdInvalid"
return c.RenderJSON(re)
}
note := noteService.GetNote(noteId, c.getUserId())
if note.NoteId == "" {
re := info.NewApiRe()
re.Msg = "notExists"
return c.RenderJSON(re)
}
apiNotes := noteService.ToApiNotes([]info.Note{note})
return c.RenderJSON(apiNotes[0])
}
// 得到note和内容
// [OK]
func (c ApiNote) GetNoteAndContent(noteId string) revel.Result {
noteAndContent := noteService.GetNoteAndContent(noteId, c.getUserId())
apiNotes := noteService.ToApiNotes([]info.Note{noteAndContent.Note})
apiNote := apiNotes[0]
apiNote.Content = noteService.FixContent(noteAndContent.Content, noteAndContent.IsMarkdown)
return c.RenderJSON(apiNote)
}
// content里的image, attach链接是
// https://leanote.com/api/file/getImage?fileId=xx
// https://leanote.com/api/file/getAttach?fileId=xx
// 将fileId=映射成ServerFileId, 这里的fileId可能是本地的FileId
func (c ApiNote) fixPostNotecontent(noteOrContent *info.ApiNote) {
if noteOrContent.Content == "" {
return
}
files := noteOrContent.Files
if files != nil && len(files) > 0 {
for _, file := range files {
if file.LocalFileId != "" {
LogJ(file)
if !file.IsAttach {
// <img src="https://"
// ![](http://demo.leanote.top/api/file/getImage?fileId=5863219465b68e4fd5000001)
reg, _ := regexp.Compile(`https*://[^/]*?/api/file/getImage\?fileId=`+file.LocalFileId)
// Log(reg)
noteOrContent.Content = reg.ReplaceAllString(noteOrContent.Content, `/api/file/getImage?fileId=`+file.FileId)
// // "http://a.com/api/file/getImage?fileId=localId" => /api/file/getImage?fileId=serverId
// noteOrContent.Content = strings.Replace(noteOrContent.Content,
// baseUrl + "/api/file/getImage?fileId="+file.LocalFileId,
// "/api/file/getImage?fileId="+file.FileId, -1)
} else {
reg, _ := regexp.Compile(`https*://[^/]*?/api/file/getAttach\?fileId=`+file.LocalFileId)
Log(reg)
noteOrContent.Content = reg.ReplaceAllString(noteOrContent.Content, `/api/file/getAttach?fileId=`+file.FileId)
/*
noteOrContent.Content = strings.Replace(noteOrContent.Content,
baseUrl + "/api/file/getAttach?fileId="+file.LocalFileId,
"/api/file/getAttach?fileId="+file.FileId, -1)
*/
}
}
}
}
}
// 得到内容
func (c ApiNote) GetNoteContent(noteId string) revel.Result {
userId := c.getUserId()
note := noteService.GetNote(noteId, userId)
// re := info.NewRe()
noteContent := noteService.GetNoteContent(noteId, userId)
if noteContent.Content != "" {
noteContent.Content = noteService.FixContent(noteContent.Content, note.IsMarkdown)
}
apiNoteContent := info.ApiNoteContent{
NoteId: noteContent.NoteId,
UserId: noteContent.UserId,
Content: noteContent.Content,
}
return c.RenderJSON(apiNoteContent)
}
// 添加笔记
// [OK]
func (c ApiNote) AddNote(noteOrContent info.ApiNote) revel.Result {
userId := bson.ObjectIdHex(c.getUserId())
re := info.NewRe()
myUserId := userId
// 为共享新建?
/*
if noteOrContent.FromUserId != "" {
userId = bson.ObjectIdHex(noteOrContent.FromUserId)
}
*/
// Log(noteOrContent.Title)
// LogJ(noteOrContent)
/*
LogJ(c.Params)
for name, _ := range c.Params.Files {
Log(name)
file, _, _ := c.Request.FormFile(name)
LogJ(file)
}
*/
// return c.RenderJSON(re)
if noteOrContent.NotebookId == "" || !bson.IsObjectIdHex(noteOrContent.NotebookId) {
re.Msg = "notebookIdNotExists"
return c.RenderJSON(re)
}
noteId := bson.NewObjectId()
// TODO 先上传图片/附件, 如果不成功, 则返回false
//
attachNum := 0
if noteOrContent.Files != nil && len(noteOrContent.Files) > 0 {
for i, file := range noteOrContent.Files {
if file.HasBody {
if file.LocalFileId != "" {
// FileDatas[54c7ae27d98d0329dd000000]
ok, msg, fileId := c.upload("FileDatas["+file.LocalFileId+"]", noteId.Hex(), file.IsAttach)
if !ok {
re.Ok = false
if msg != "" {
Log(msg)
Log(file.LocalFileId)
re.Msg = "fileUploadError"
}
// 报不是图片的错误没关系, 证明客户端传来非图片的数据
if msg != "notImage" {
return c.RenderJSON(re)
}
} else {
// 建立映射
file.FileId = fileId
noteOrContent.Files[i] = file
if file.IsAttach {
attachNum++
}
}
} else {
return c.RenderJSON(re)
}
}
}
}
c.fixPostNotecontent(&noteOrContent)
// Log("Add")
// LogJ(noteOrContent)
// return c.RenderJSON(re)
note := info.Note{UserId: userId,
NoteId: noteId,
NotebookId: bson.ObjectIdHex(noteOrContent.NotebookId),
Title: noteOrContent.Title,
Tags: noteOrContent.Tags,
Desc: noteOrContent.Desc,
// ImgSrc: noteOrContent.ImgSrc,
IsBlog: noteOrContent.IsBlog,
IsMarkdown: noteOrContent.IsMarkdown,
AttachNum: attachNum,
CreatedTime: noteOrContent.CreatedTime,
UpdatedTime: noteOrContent.UpdatedTime,
}
noteContent := info.NoteContent{NoteId: note.NoteId,
UserId: userId,
IsBlog: note.IsBlog,
Content: noteOrContent.Content,
Abstract: noteOrContent.Abstract,
CreatedTime: noteOrContent.CreatedTime,
UpdatedTime: noteOrContent.UpdatedTime,
}
// 通过内容得到Desc, abstract
if noteOrContent.Abstract == "" {
note.Desc = SubStringHTMLToRaw(noteContent.Content, 200)
noteContent.Abstract = SubStringHTML(noteContent.Content, 200, "")
} else {
note.Desc = SubStringHTMLToRaw(noteContent.Abstract, 200)
}
note = noteService.AddNoteAndContentApi(note, noteContent, myUserId)
if note.NoteId == "" {
re.Ok = false
return c.RenderJSON(re)
}
// 添加需要返回的
noteOrContent.NoteId = note.NoteId.Hex()
noteOrContent.Usn = note.Usn
noteOrContent.CreatedTime = note.CreatedTime
noteOrContent.UpdatedTime = note.UpdatedTime
noteOrContent.UserId = c.getUserId()
noteOrContent.IsMarkdown = note.IsMarkdown
// 删除一些不要返回的, 删除Desc?
noteOrContent.Content = ""
noteOrContent.Abstract = ""
// apiNote := info.NoteToApiNote(note, noteOrContent.Files)
return c.RenderJSON(noteOrContent)
}
// 更新笔记
// [OK]
func (c ApiNote) UpdateNote(noteOrContent info.ApiNote) revel.Result {
re := info.NewReUpdate()
noteUpdate := bson.M{}
needUpdateNote := false
noteId := noteOrContent.NoteId
if noteOrContent.NoteId == "" {
re.Msg = "noteIdNotExists"
return c.RenderJSON(re)
}
if noteOrContent.Usn <= 0 {
re.Msg = "usnNotExists"
return c.RenderJSON(re)
}
// Log("_____________")
// LogJ(noteOrContent)
/*
LogJ(c.Params.Files)
LogJ(c.Request.Header)
LogJ(c.Params.Values)
*/
// 先判断USN的问题, 因为很可能添加完附件后, 会有USN冲突, 这时附件就添错了
userId := c.getUserId()
note := noteService.GetNote(noteId, userId)
if note.NoteId == "" {
re.Msg = "notExists"
return c.RenderJSON(re)
}
if note.Usn != noteOrContent.Usn {
re.Msg = "conflict"
Log("conflict")
return c.RenderJSON(re)
}
Log("没有冲突")
// 如果传了files
// TODO 测试
/*
for key, v := range c.Params.Values {
Log(key)
Log(v)
}
*/
// Log(c.Has("Files[0]"))
if c.Has("Files[0][LocalFileId]") {
// LogJ(c.Params.Files)
if noteOrContent.Files != nil && len(noteOrContent.Files) > 0 {
for i, file := range noteOrContent.Files {
if file.HasBody {
if file.LocalFileId != "" {
// FileDatas[54c7ae27d98d0329dd000000]
ok, msg, fileId := c.upload("FileDatas["+file.LocalFileId+"]", noteId, file.IsAttach)
if !ok {
Log("upload file error")
re.Ok = false
if msg == "" {
re.Msg = "fileUploadError"
} else {
re.Msg = msg
}
return c.RenderJSON(re)
} else {
// 建立映射
file.FileId = fileId
noteOrContent.Files[i] = file
}
} else {
return c.RenderJSON(re)
}
}
}
}
// Log("after upload")
// LogJ(noteOrContent.Files)
}
// 移到外面来, 删除最后一个file时也要处理, 不然总删不掉
// 附件问题, 根据Files, 有些要删除的, 只留下这些
attachService.UpdateOrDeleteAttachApi(noteId, userId, noteOrContent.Files)
// Desc前台传来
if c.Has("Desc") {
needUpdateNote = true
noteUpdate["Desc"] = noteOrContent.Desc
}
/*
if c.Has("ImgSrc") {
needUpdateNote = true
noteUpdate["ImgSrc"] = noteOrContent.ImgSrc
}
*/
if c.Has("Title") {
needUpdateNote = true
noteUpdate["Title"] = noteOrContent.Title
}
if c.Has("IsTrash") {
needUpdateNote = true
noteUpdate["IsTrash"] = noteOrContent.IsTrash
}
// 是否是博客
if c.Has("IsBlog") {
needUpdateNote = true
noteUpdate["IsBlog"] = noteOrContent.IsBlog
}
/*
Log(c.Has("tags[0]"))
Log(c.Has("Tags[]"))
for key, v := range c.Params.Values {
Log(key)
Log(v)
}
*/
if c.Has("Tags[0]") {
needUpdateNote = true
noteUpdate["Tags"] = noteOrContent.Tags
}
if c.Has("NotebookId") {
if bson.IsObjectIdHex(noteOrContent.NotebookId) {
needUpdateNote = true
noteUpdate["NotebookId"] = bson.ObjectIdHex(noteOrContent.NotebookId)
}
}
if c.Has("Content") {
// 通过内容得到Desc, 如果有Abstract, 则用Abstract生成Desc
if noteOrContent.Abstract == "" {
noteUpdate["Desc"] = SubStringHTMLToRaw(noteOrContent.Content, 200)
} else {
noteUpdate["Desc"] = SubStringHTMLToRaw(noteOrContent.Abstract, 200)
}
}
noteUpdate["UpdatedTime"] = noteOrContent.UpdatedTime
afterNoteUsn := 0
noteOk := false
noteMsg := ""
if needUpdateNote {
noteOk, noteMsg, afterNoteUsn = noteService.UpdateNote(c.getUserId(), noteOrContent.NoteId, noteUpdate, noteOrContent.Usn)
if !noteOk {
re.Ok = false
re.Msg = noteMsg
return c.RenderJSON(re)
}
}
//-------------
afterContentUsn := 0
contentOk := false
contentMsg := ""
if c.Has("Content") {
// 把fileId替换下
c.fixPostNotecontent(&noteOrContent)
// 如果传了Abstract就用之
if noteOrContent.Abstract == "" {
noteOrContent.Abstract = SubStringHTML(noteOrContent.Content, 200, "")
}
// Log("--------> afte fixed")
// Log(noteOrContent.Content)
contentOk, contentMsg, afterContentUsn = noteService.UpdateNoteContent(c.getUserId(),
noteOrContent.NoteId,
noteOrContent.Content,
noteOrContent.Abstract,
needUpdateNote,
noteOrContent.Usn,
noteOrContent.UpdatedTime)
}
if needUpdateNote {
re.Ok = noteOk
re.Msg = noteMsg
re.Usn = afterNoteUsn
} else {
re.Ok = contentOk
re.Msg = contentMsg
re.Usn = afterContentUsn
}
if !re.Ok {
return c.RenderJSON(re)
}
noteOrContent.Content = ""
noteOrContent.Usn = re.Usn
noteOrContent.UpdatedTime = time.Now()
// Log("after upload")
// LogJ(noteOrContent.Files)
noteOrContent.UserId = c.getUserId()
return c.RenderJSON(noteOrContent)
}
// 删除trash
func (c ApiNote) DeleteTrash(noteId string, usn int) revel.Result {
re := info.NewReUpdate()
re.Ok, re.Msg, re.Usn = trashService.DeleteTrashApi(noteId, c.getUserId(), usn)
return c.RenderJSON(re)
}
// 得到历史列表
/*
func (c ApiNote) GetHistories(noteId string) revel.Result {
re := info.NewRe()
histories := noteContentHistoryService.ListHistories(noteId, c.getUserId())
if len(histories) > 0 {
re.Ok = true
re.Item = histories
}
return c.RenderJSON(re)
}
*/
// 0.2 新增
// 导出成PDF
func (c ApiNote) ExportPdf(noteId string) revel.Result {
re := info.NewApiRe()
userId := c.getUserId()
if noteId == "" {
re.Msg = "noteNotExists"
return c.RenderJSON(re)
}
note := noteService.GetNoteById(noteId)
if note.NoteId == "" {
re.Msg = "noteNotExists"
return c.RenderJSON(re)
}
noteUserId := note.UserId.Hex()
// 是否有权限
if noteUserId != userId {
// 是否是有权限协作的
if !note.IsBlog && !shareService.HasReadPerm(noteUserId, userId, noteId) {
re.Msg = "noteNotExists"
return c.RenderJSON(re)
}
}
// path 判断是否需要重新生成之
guid := NewGuid()
fileUrlPath := "files/export_pdf"
dir := revel.BasePath + "/" + fileUrlPath
if !MkdirAll(dir) {
re.Msg = "noDir"
return c.RenderJSON(re)
}
filename := guid + ".pdf"
path := dir + "/" + filename
appKey, _ := revel.Config.String("app.secretLeanote")
if appKey == "" {
appKey, _ = revel.Config.String("app.secret")
}
// 生成之
binPath := configService.GetGlobalStringConfig("exportPdfBinPath")
// 默认路径
if binPath == "" {
binPath = "/usr/local/bin/wkhtmltopdf"
}
url := configService.GetSiteUrl() + "/note/toPdf?noteId=" + noteId + "&appKey=" + appKey
var cc string
if note.IsMarkdown {
cc = binPath + " --lowquality --window-status done \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\""
} else {
cc = binPath + " --lowquality \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\""
}
cmd := exec.Command("/bin/sh", "-c", cc)
_, err := cmd.Output()
if err != nil {
re.Msg = "sysError"
return c.RenderJSON(re)
}
file, err := os.Open(path)
if err != nil {
re.Msg = "sysError"
return c.RenderJSON(re)
}
filenameReturn := note.Title
filenameReturn = FixFilename(filenameReturn)
if filenameReturn == "" {
filenameReturn = "Untitled.pdf"
} else {
filenameReturn += ".pdf"
}
return c.RenderBinary(file, filenameReturn, revel.Attachment, time.Now()) // revel.Attachment
}

View File

@@ -0,0 +1,106 @@
package api
import (
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
// "io/ioutil"
)
// 笔记本API
type ApiNotebook struct {
ApiBaseContrller
}
// 从Notebook -> ApiNotebook
func (c ApiNotebook) fixNotebooks(notebooks []info.Notebook) []info.ApiNotebook {
if notebooks == nil {
return nil
}
apiNotebooks := make([]info.ApiNotebook, len(notebooks))
for i, notebook := range notebooks {
apiNotebooks[i] = c.fixNotebook(&notebook)
}
return apiNotebooks
}
func (c ApiNotebook) fixNotebook(notebook *info.Notebook) info.ApiNotebook {
if notebook == nil {
return info.ApiNotebook{}
}
return info.ApiNotebook{
NotebookId: notebook.NotebookId,
UserId: notebook.UserId,
ParentNotebookId: notebook.ParentNotebookId,
Seq: notebook.Seq,
Title: notebook.Title,
UrlTitle: notebook.UrlTitle,
IsBlog: notebook.IsBlog,
CreatedTime: notebook.CreatedTime,
UpdatedTime: notebook.UpdatedTime,
Usn: notebook.Usn,
IsDeleted: notebook.IsDeleted,
}
}
// 获取同步的笔记本
// [OK]
// > afterUsn的笔记
// 返回 {ChunkHighUsn: 本下最大的usn, 借此可以知道是否还有, Notebooks: []}
func (c ApiNotebook) GetSyncNotebooks(afterUsn, maxEntry int) revel.Result {
if maxEntry == 0 {
maxEntry = 100
}
notebooks := notebookService.GeSyncNotebooks(c.getUserId(), afterUsn, maxEntry)
return c.RenderJSON(c.fixNotebooks(notebooks))
}
// 得到用户的所有笔记本
// [OK]
// info.SubNotebooks
func (c ApiNotebook) GetNotebooks() revel.Result {
notebooks := notebookService.GeSyncNotebooks(c.getUserId(), 0, 99999)
return c.RenderJSON(c.fixNotebooks(notebooks))
}
// 添加notebook
// [OK]
func (c ApiNotebook) AddNotebook(title, parentNotebookId string, seq int) revel.Result {
notebook := info.Notebook{NotebookId: bson.NewObjectId(),
Title: title,
Seq: seq,
UserId: bson.ObjectIdHex(c.getUserId())}
if parentNotebookId != "" && bson.IsObjectIdHex(parentNotebookId) {
notebook.ParentNotebookId = bson.ObjectIdHex(parentNotebookId)
}
re := info.NewRe()
re.Ok, notebook = notebookService.AddNotebook(notebook)
if !re.Ok {
return c.RenderJSON(re)
}
return c.RenderJSON(c.fixNotebook(&notebook))
}
// 修改笔记
// [OK]
func (c ApiNotebook) UpdateNotebook(notebookId, title, parentNotebookId string, seq, usn int) revel.Result {
re := info.NewApiRe()
ok, msg, notebook := notebookService.UpdateNotebookApi(c.getUserId(), notebookId, title, parentNotebookId, seq, usn)
if !ok {
re.Ok = false
re.Msg = msg
return c.RenderJSON(re)
}
LogJ(notebook)
return c.RenderJSON(c.fixNotebook(&notebook))
}
// 删除笔记本
// [OK]
func (c ApiNotebook) DeleteNotebook(notebookId string, usn int) revel.Result {
re := info.NewApiRe()
re.Ok, re.Msg = notebookService.DeleteNotebookForce(c.getUserId(), notebookId, usn)
return c.RenderJSON(re)
}

View File

@@ -0,0 +1,56 @@
package api
import (
"github.com/leanote/leanote/app/info"
"github.com/revel/revel"
// "gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
)
// 标签API
type ApiTag struct {
ApiBaseContrller
}
// 获取同步的标签
// [OK]
// > afterUsn的笔记
// 返回 {ChunkHighUsn: 本下最大的usn, 借此可以知道是否还有, Notebooks: []}
func (c ApiTag) GetSyncTags(afterUsn, maxEntry int) revel.Result {
if maxEntry == 0 {
maxEntry = 100
}
tags := tagService.GeSyncTags(c.getUserId(), afterUsn, maxEntry)
return c.RenderJSON(tags)
}
// 添加Tag
// [OK]
// 不会产生冲突, 即使里面有
// 返回
/*
{
"TagId": "551978dd99c37b9bc5000001",
"UserId": "54a1676399c37b1c77000002",
"Tag": "32",
"Usn": 25,
"Count": 1,
"CreatedTime": "2015-03-31T00:25:01.149312407+08:00",
"UpdatedTime": "2015-03-31T00:25:01.149312407+08:00",
"IsDeleted": false
}
*/
func (c ApiTag) AddTag(tag string) revel.Result {
ret := tagService.AddOrUpdateTag(c.getUserId(), tag)
return c.RenderJSON(ret)
}
// 删除标签
// [OK]
func (c ApiTag) DeleteTag(tag string, usn int) revel.Result {
re := info.NewReUpdate()
re.Ok, re.Msg, re.Usn = tagService.DeleteTagApi(c.getUserId(), tag, usn)
return c.RenderJSON(re)
}

View File

@@ -0,0 +1,154 @@
package api
import (
"github.com/revel/revel"
// "encoding/json"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"time"
// "github.com/leanote/leanote/app/types"
"io/ioutil"
// "fmt"
// "math"
"os"
// "path"
// "strconv"
)
type ApiUser struct {
ApiBaseContrller
}
// 获取用户信息
// [OK]
func (c ApiUser) Info() revel.Result {
re := info.NewApiRe()
userInfo := c.getUserInfo()
if userInfo.UserId == "" {
return c.RenderJSON(re)
}
apiUser := info.ApiUser{
UserId: userInfo.UserId.Hex(),
Username: userInfo.Username,
Email: userInfo.Email,
Logo: userInfo.Logo,
Verified: userInfo.Verified,
}
return c.RenderJSON(apiUser)
}
// 修改用户名
// [OK]
func (c ApiUser) UpdateUsername(username string) revel.Result {
re := info.NewApiRe()
if c.GetUsername() == "demo" {
re.Msg = "cannotUpdateDemo"
return c.RenderJSON(re)
}
if re.Ok, re.Msg = Vd("username", username); !re.Ok {
return c.RenderJSON(re)
}
re.Ok, re.Msg = userService.UpdateUsername(c.getUserId(), username)
return c.RenderJSON(re)
}
// 修改密码
// [OK]
func (c ApiUser) UpdatePwd(oldPwd, pwd string) revel.Result {
re := info.NewApiRe()
if c.GetUsername() == "demo" {
re.Msg = "cannotUpdateDemo"
return c.RenderJSON(re)
}
if re.Ok, re.Msg = Vd("password", oldPwd); !re.Ok {
return c.RenderJSON(re)
}
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderJSON(re)
}
re.Ok, re.Msg = userService.UpdatePwd(c.getUserId(), oldPwd, pwd)
return c.RenderJSON(re)
}
// 获得同步状态
// [OK]
func (c ApiUser) GetSyncState() revel.Result {
ret := bson.M{"LastSyncUsn": userService.GetUsn(c.getUserId()), "LastSyncTime": time.Now().Unix()}
return c.RenderJSON(ret)
}
// 头像设置
// 参数file=文件
// 成功返回{Logo: url} 头像新url
// [OK]
func (c ApiUser) UpdateLogo() revel.Result {
ok, msg, url := c.uploadImage()
if ok {
ok = userService.UpdateAvatar(c.getUserId(), url)
return c.RenderJSON(map[string]string{"Logo": url})
} else {
re := info.NewApiRe()
re.Msg = msg
return c.RenderJSON(re)
}
}
// 上传图片
func (c ApiUser) uploadImage() (ok bool, msg, url string) {
var fileUrlPath = ""
ok = false
file, handel, err := c.Request.FormFile("file")
if err != nil {
return
}
defer file.Close()
// 生成上传路径
fileUrlPath = "public/upload/" + c.getUserId() + "/images/logo"
dir := revel.BasePath + "/" + fileUrlPath
err = os.MkdirAll(dir, 0755)
if err != nil {
return
}
// 生成新的文件名
filename := handel.Filename
var ext string
_, ext = SplitFilename(filename)
if ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg" {
msg = "notImage"
return
}
filename = NewGuid() + ext
data, err := ioutil.ReadAll(file)
if err != nil {
LogJ(err)
return
}
// > 5M?
if len(data) > 5*1024*1024 {
msg = "fileIsTooLarge"
return
}
toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
LogJ(err)
return
}
ok = true
url = configService.GetSiteUrl() + "/" + fileUrlPath + "/" + filename
return
}

144
app/controllers/api/init.go Normal file
View File

@@ -0,0 +1,144 @@
package api
import (
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/service"
// . "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"strings"
)
var userService *service.UserService
var noteService *service.NoteService
var trashService *service.TrashService
var notebookService *service.NotebookService
var noteContentHistoryService *service.NoteContentHistoryService
var authService *service.AuthService
var shareService *service.ShareService
var blogService *service.BlogService
var tagService *service.TagService
var pwdService *service.PwdService
var tokenService *service.TokenService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
var noteImageService *service.NoteImageService
var fileService *service.FileService
var attachService *service.AttachService
var configService *service.ConfigService
var emailService *service.EmailService
var sessionService *service.SessionService
var pageSize = 1000
var defaultSortField = "UpdatedTime"
var leanoteUserId = "admin" // 不能更改
// 状态
const (
S_DEFAULT = iota // 0
S_NOT_LOGIN // 1
S_WRONG_USERNAME_PASSWORD // 2
S_WRONG_CAPTCHA // 3
S_NEED_CAPTCHA // 4
S_NOT_OPEN_REGISTER // 4
)
// 拦截器
// 不需要拦截的url
var commonUrl = map[string]map[string]bool{"ApiAuth": map[string]bool{"Login": true,
"Register": true,
},
// 文件的操作也不用登录, userId会从session中获取
"ApiFile": map[string]bool{"GetImage": true,
"GetAttach": true,
"GetAllAttachs": true,
},
}
func needValidate(controller, method string) bool {
// 在里面
if v, ok := commonUrl[controller]; ok {
// 在commonUrl里
if _, ok2 := v[method]; ok2 {
return false
}
return true
} else {
// controller不在这里的, 肯定要验证
return true
}
}
// 这里得到token, 若不是login, logout等公用操作, 必须验证是否已登录
func AuthInterceptor(c *revel.Controller) revel.Result {
// 得到token /api/user/info?userId=xxx&token=xxxxx
token := c.Params.Values.Get("token")
noToken := false
if token == "" {
// 若无, 则取sessionId
token = c.Session.ID()
noToken = true
}
c.Session["_token"] = token
// 全部变成首字大写
var controller = strings.Title(c.Name)
var method = strings.Title(c.MethodName)
// 验证是否已登录
// 通过sessionService判断该token下是否有userId, 并返回userId
userId := sessionService.GetUserId(token)
if noToken && userId == "" {
// 从session中获取, api/file/getImage, api/file/getAttach, api/file/getAllAttach
// 客户端
userId, _ = c.Session["UserId"]
}
c.Session["_userId"] = userId
// 是否需要验证?
if !needValidate(controller, method) {
return nil
}
if userId != "" {
return nil // 已登录
}
// 没有登录, 返回错误的信息, 需要登录
re := info.NewApiRe()
re.Msg = "NOTLOGIN"
return c.RenderJSON(re)
}
func init() {
// interceptors
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiAuth{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiUser{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiFile{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiNote{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiTag{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiNotebook{})
}
// 最外层init.go调用
// 获取service, 单例
func InitService() {
notebookService = service.NotebookS
noteService = service.NoteS
noteContentHistoryService = service.NoteContentHistoryS
trashService = service.TrashS
shareService = service.ShareS
userService = service.UserS
tagService = service.TagS
blogService = service.BlogS
tokenService = service.TokenS
noteImageService = service.NoteImageS
fileService = service.FileS
albumService = service.AlbumS
attachService = service.AttachS
pwdService = service.PwdS
suggestionService = service.SuggestionS
authService = service.AuthS
configService = service.ConfigS
emailService = service.EmailS
sessionService = service.SessionS
}

View File

@@ -1,103 +1,144 @@
package controllers
import (
"github.com/leanote/leanote/app/service"
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/lea/blog"
"github.com/leanote/leanote/app/service"
// . "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"strings"
)
// 该文件初始化所有service方法
var userService *service.UserService
var noteService *service.NoteService
var trashService *service.TrashService
var notebookService *service.NotebookService
var noteContentHistoryService *service.NoteContentHistoryService
var authService *service.AuthService
var shareService *service.ShareService
var blogService *service.BlogService
var tagService *service.TagService
var pwdService *service.PwdService
var tokenService *service.TokenService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
var noteImageService *service.NoteImageService
var fileService *service.FileService
var attachService *service.AttachService
var configService *service.ConfigService
var emailService *service.EmailService
var sessionService *service.SessionService
var themeService *service.ThemeService
var pageSize = 1000
var defaultSortField = "UpdatedTime"
var leanoteUserId = "52d26b4e99c37b609a000001"
var siteUrl = "http://leanote.com"
var openRegister = true
// 拦截器
// 不需要拦截的url
// Index 除了Note之外都不需要
var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": true,
"Login": true,
"DoLogin": true,
"Logout": true,
"Register": true,
"DoRegister": true,
"FindPasswword": true,
"DoFindPassword": true,
"FindPassword2": true,
"FindPasswordUpdate": true,
"Suggestion": true,
},
var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": true,
"Login": true,
"DoLogin": true,
"Logout": true,
"Register": true,
"DoRegister": true,
"FindPasswword": true,
"DoFindPassword": true,
"FindPassword2": true,
"FindPasswordUpdate": true,
"Suggestion": true,
},
"Note": map[string]bool{"ToPdf": true},
"Blog": map[string]bool{"Index": true,
"View": true,
"AboutMe": true,
"SearchBlog": true,
},
"View": true,
"AboutMe": true,
"Cate": true,
"ListCateLatest": true,
"Search": true,
"GetLikeAndComments": true,
"IncReadNum": true,
"ListComments": true,
"Single": true,
"Archive": true,
"Tags": true,
},
// 用户的激活与修改邮箱都不需要登录, 通过链接地址
"User": map[string]bool{"UpdateEmail": true,
"ActiveEmail":true,
},
"oauth": map[string]bool{"githubCallback": true},
"ActiveEmail": true,
},
"Oauth": map[string]bool{"GithubCallback": true},
"File": map[string]bool{"OutputImage": true, "OutputFile": true},
"Attach": map[string]bool{"Download": true /*, "DownloadAll": true*/},
}
func needValidate(controller, method string) bool {
// 在里面
if v, ok := commonUrl[controller]; ok {
// 在commonUrl里
// 在commonUrl里
if _, ok2 := v[method]; ok2 {
return false
}
return true
} else {
// controller不在这里的, 肯定要验证
return true;
return true
}
}
func AuthInterceptor(c *revel.Controller) revel.Result {
// 全部变成首字大写
var controller = strings.Title(c.Name)
var method = strings.Title(c.MethodName)
// 是否需要验证?
if !needValidate(controller, method) {
return nil
}
// 验证是否已登录
if userId, ok := c.Session["UserId"]; ok && userId != "" {
return nil // 已登录
}
// 没有登录, 判断是否是ajax操作
if c.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
re := info.NewRe()
re.Msg = "NOTLOGIN"
return c.RenderJson(re)
return c.RenderJSON(re)
}
return c.Redirect("/login")
}
// 最外层init.go调用
// 获取service, 单例
func InitService() {
notebookService = service.NotebookS
noteService = service.NoteS
noteContentHistoryService = service.NoteContentHistoryS
trashService = service.TrashS
shareService = service.ShareS
userService = service.UserS
tagService = service.TagS
blogService = service.BlogS
tokenService = service.TokenS
noteImageService = service.NoteImageS
fileService = service.FileS
albumService = service.AlbumS
attachService = service.AttachS
pwdService = service.PwdS
suggestionService = service.SuggestionS
authService = service.AuthS
configService = service.ConfigS
emailService = service.EmailS
sessionService = service.SessionS
themeService = service.ThemeS
}
// 初始化博客模板
// 博客模板不由revel的
func initBlogTemplate() {
}
func init() {
// interceptor
// revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Index{}) // Index.Note自己校验
@@ -105,28 +146,14 @@ func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Note{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Share{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &User{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Album{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &File{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Blog{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Attach{})
// revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Blog{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &NoteContentHistory{})
// service
userService = &service.UserService{}
noteService = &service.NoteService{}
trashService = &service.TrashService{}
notebookService = &service.NotebookService{}
noteContentHistoryService = &service.NoteContentHistoryService{}
authService = &service.AuthService{}
shareService = &service.ShareService{}
blogService = &service.BlogService{}
tagService = &service.TagService{}
pwdService = &service.PwdService{}
tokenService = &service.TokenService{}
suggestionService = &service.SuggestionService{}
revel.OnAppStart(func() {
leanoteUserId, _ = revel.Config.String("adminUsername")
siteUrl, _ = revel.Config.String("site.url")
openRegister, _ = revel.Config.Bool("register.open")
// 博客初始化模板
blog.Init()
})
}
}

View File

@@ -0,0 +1,59 @@
package member
import (
// "github.com/revel/revel"
// "gopkg.in/mgo.v2/bson"
// "encoding/json"
"github.com/leanote/leanote/app/controllers"
. "github.com/leanote/leanote/app/lea"
// "io/ioutil"
// "fmt"
// "math"
// "strconv"
"strings"
)
// 公用Controller, 其它Controller继承它
type MemberBaseController struct {
controllers.BaseController // 不能用*BaseController
}
// 得到sorterField 和 isAsc
// okSorter = ['email', 'username']
func (c MemberBaseController) getSorter(sorterField string, isAsc bool, okSorter []string) (string, bool) {
sorter := ""
c.Params.Bind(&sorter, "sorter")
if sorter == "" {
return sorterField, isAsc
}
// sorter形式 email-up, email-down
s2 := strings.Split(sorter, "-")
if len(s2) != 2 {
return sorterField, isAsc
}
// 必须是可用的sorter
if okSorter != nil && len(okSorter) > 0 {
if !InArray(okSorter, s2[0]) {
return sorterField, isAsc
}
}
sorterField = strings.Title(s2[0])
if s2[1] == "up" {
isAsc = true
} else {
isAsc = false
}
c.ViewArgs["sorter"] = sorter
return sorterField, isAsc
}
func (c MemberBaseController) updateConfig(keys []string) {
userId := c.GetUserId()
for _, key := range keys {
v := c.Params.Values.Get(key)
configService.UpdateGlobalStringConfig(userId, key, v)
}
}

View File

@@ -0,0 +1,528 @@
package member
import (
"fmt"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"io/ioutil"
"os"
"strings"
"time"
// "github.com/leanote/leanote/app/lea/blog"
)
// 博客管理
type MemberBlog struct {
MemberBaseController
}
func (c MemberBlog) common() info.UserBlog {
userId := c.GetUserId()
userInfo := userService.GetUserInfo(userId)
c.ViewArgs["userInfo"] = userInfo
// 得到博客设置信息
c.ViewArgs["allowCustomDomain"] = configService.GetGlobalStringConfig("allowCustomDomain")
userBlog := blogService.GetUserBlog(userId)
c.ViewArgs["userBlog"] = userBlog
c.SetUserInfo()
c.SetLocale()
return userBlog
}
// 得到sorterField 和 isAsc
// okSorter = ['email', 'username']
func (c MemberBlog) getSorter(sorterField string, isAsc bool, okSorter []string) (string, bool) {
sorter := ""
c.Params.Bind(&sorter, "sorter")
if sorter == "" {
return sorterField, isAsc
}
// sorter形式 email-up, email-down
s2 := strings.Split(sorter, "-")
if len(s2) != 2 {
return sorterField, isAsc
}
// 必须是可用的sorter
if okSorter != nil && len(okSorter) > 0 {
if !InArray(okSorter, s2[0]) {
return sorterField, isAsc
}
}
sorterField = strings.Title(s2[0])
if s2[1] == "up" {
isAsc = true
} else {
isAsc = false
}
c.ViewArgs["sorter"] = sorter
return sorterField, isAsc
}
// 博客列表
var userPageSize = 15
func (c MemberBlog) Index(sorter, keywords string) revel.Result {
userId := c.GetUserId()
userInfo := userService.GetUserInfo(userId)
c.ViewArgs["userInfo"] = userInfo
c.ViewArgs["title"] = c.Message("Posts")
pageNumber := c.GetPage()
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"title", "urlTitle", "updatedTime", "publicTime", "createdTime"})
pageInfo, blogs := blogService.ListAllBlogs(c.GetUserId(), "", keywords, false, pageNumber, userPageSize, sorterField, isAsc)
c.ViewArgs["pageInfo"] = pageInfo
c.ViewArgs["blogs"] = blogs
c.ViewArgs["keywords"] = keywords
userAndBlog := userService.GetUserAndBlog(c.GetUserId())
c.ViewArgs["userAndBlog"] = userAndBlog
c.common()
return c.RenderTemplate("member/blog/list.html")
}
// 修改笔记的urlTitle
func (c MemberBlog) UpdateBlogUrlTitle(noteId, urlTitle string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = blogService.UpateBlogUrlTitle(c.GetUserId(), noteId, urlTitle)
return c.RenderJSON(re)
}
// 修改笔记的urlTitle
func (c MemberBlog) UpdateBlogAbstract(noteId string) revel.Result {
c.common()
c.ViewArgs["title"] = c.Message("Update Post Abstract")
note := noteService.GetNoteAndContent(noteId, c.GetUserId())
if !note.Note.IsBlog {
return c.E404()
}
c.ViewArgs["note"] = note
c.ViewArgs["noteId"] = noteId
return c.RenderTemplate("member/blog/update_abstract.html")
}
func (c MemberBlog) DoUpdateBlogAbstract(noteId, imgSrc, desc, abstract string) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpateBlogAbstract(c.GetUserId(), noteId, imgSrc, desc, abstract)
return c.RenderJSON(re)
}
// 基本信息设置
func (c MemberBlog) Base() revel.Result {
c.common()
c.ViewArgs["title"] = c.Message("Blog Base Info")
return c.RenderTemplate("member/blog/base.html")
}
func (c MemberBlog) Comment() revel.Result {
c.common()
c.ViewArgs["title"] = c.Message("Comment")
return c.RenderTemplate("member/blog/comment.html")
}
func (c MemberBlog) Paging() revel.Result {
c.common()
c.ViewArgs["title"] = c.Message("Paging")
return c.RenderTemplate("member/blog/paging.html")
}
func (c MemberBlog) Cate() revel.Result {
userBlog := c.common()
c.ViewArgs["title"] = c.Message("Category")
notebooks := blogService.ListBlogNotebooks(c.GetUserId())
notebooksMap := map[string]info.Notebook{}
for _, each := range notebooks {
notebooksMap[each.NotebookId.Hex()] = each
}
var i = 0
notebooks2 := make([]info.Notebook, len(notebooks))
// 先要保证已有的是正确的排序
cateIds := userBlog.CateIds
has := map[string]bool{} // cateIds中有的
if cateIds != nil && len(cateIds) > 0 {
for _, cateId := range cateIds {
if n, ok := notebooksMap[cateId]; ok {
notebooks2[i] = n
i++
has[cateId] = true
}
}
}
// 之后
for _, each := range notebooks {
id := each.NotebookId.Hex()
if !has[id] {
notebooks2[i] = each
i++
}
}
c.ViewArgs["notebooks"] = notebooks2
return c.RenderTemplate("member/blog/cate.html")
}
// 修改分类排序
func (c MemberBlog) UpateCateIds(cateIds []string) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpateCateIds(c.GetUserId(), cateIds)
return c.RenderJSON(re)
}
func (c MemberBlog) UpdateCateUrlTitle(cateId, urlTitle string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = blogService.UpateCateUrlTitle(c.GetUserId(), cateId, urlTitle)
return c.RenderJSON(re)
}
// 保存之, 包含增加与保存
func (c MemberBlog) DoAddOrUpdateSingle(singleId, title, content string) revel.Result {
re := info.NewRe()
re.Ok = blogService.AddOrUpdateSingle(c.GetUserId(), singleId, title, content)
return c.RenderJSON(re)
}
func (c MemberBlog) AddOrUpdateSingle(singleId string) revel.Result {
c.common()
c.ViewArgs["title"] = c.Message("Add Single")
c.ViewArgs["singleId"] = singleId
if singleId != "" {
c.ViewArgs["title"] = c.Message("Update Single")
c.ViewArgs["single"] = blogService.GetSingle(singleId)
}
return c.RenderTemplate("member/blog/add_single.html")
}
func (c MemberBlog) SortSingles(singleIds []string) revel.Result {
re := info.NewRe()
re.Ok = blogService.SortSingles(c.GetUserId(), singleIds)
return c.RenderJSON(re)
}
func (c MemberBlog) DeleteSingle(singleId string) revel.Result {
re := info.NewRe()
re.Ok = blogService.DeleteSingle(c.GetUserId(), singleId)
return c.RenderJSON(re)
}
// 修改页面标题
func (c MemberBlog) UpdateSingleUrlTitle(singleId, urlTitle string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = blogService.UpdateSingleUrlTitle(c.GetUserId(), singleId, urlTitle)
return c.RenderJSON(re)
}
func (c MemberBlog) Single() revel.Result {
c.common()
c.ViewArgs["title"] = c.Message("Single")
c.ViewArgs["singles"] = blogService.GetSingles(c.GetUserId())
return c.RenderTemplate("member/blog/single.html")
}
// 主题
func (c MemberBlog) Theme() revel.Result {
c.common()
activeTheme, otherThemes := themeService.GetUserThemes(c.GetUserId())
c.ViewArgs["activeTheme"] = activeTheme
c.ViewArgs["otherThemes"] = otherThemes
c.ViewArgs["optionThemes"] = themeService.GetDefaultThemes()
c.ViewArgs["title"] = c.Message("Theme")
return c.RenderTemplate("member/blog/theme.html")
}
// 编辑主题
var baseTpls = []string{"header.html", "footer.html", "index.html", "cate.html", "search.html", "post.html", "single.html", "tags.html", "tag_posts.html", "archive.html", "share_comment.html", "404.html", "theme.json", "style.css", "blog.js"}
func (c MemberBlog) UpdateTheme(themeId string, isNew int) revel.Result {
// 查看用户是否有该theme, 若没有则复制default之
// 得到主题的文件列表
userBlog := blogService.GetUserBlog(c.GetUserId())
if themeId == "" {
_, themeId = themeService.NewThemeForFirst(userBlog)
return c.Redirect("/member/blog/updateTheme?themeId=" + themeId)
}
c.common()
c.ViewArgs["title"] = c.Message("Update Theme")
c.ViewArgs["isNew"] = isNew
// 先复制之
c.ViewArgs["themeId"] = themeId
// 得到脚本目录
userId := c.GetUserId()
theme := themeService.GetTheme(userId, themeId)
if theme.ThemeId == "" {
return c.E404()
}
c.ViewArgs["theme"] = theme
path := revel.BasePath + "/" + theme.Path
tpls := ListDir(path)
myTpls := make([]string, len(baseTpls))
tplMap := map[string]bool{}
for i, t := range baseTpls {
myTpls[i] = t
tplMap[t] = true
}
// 得到没有的tpls
for _, t := range tpls {
if t == "images" {
continue
}
if !tplMap[t] {
myTpls = append(myTpls, t)
}
}
c.ViewArgs["myTpls"] = myTpls
return c.RenderTemplate("member/blog/update_theme.html")
}
// 得到文件内容
func (c MemberBlog) GetTplContent(themeId string, filename string) revel.Result {
re := info.NewRe()
re.Ok = true
re.Item = themeService.GetTplContent(c.GetUserId(), themeId, filename)
return c.RenderJSON(re)
}
func (c MemberBlog) UpdateTplContent(themeId, filename, content string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = themeService.UpdateTplContent(c.GetUserId(), themeId, filename, content)
return c.RenderRe(re)
}
func (c MemberBlog) DeleteTpl(themeId, filename string) revel.Result {
re := info.NewRe()
re.Ok = themeService.DeleteTpl(c.GetUserId(), themeId, filename)
return c.RenderJSON(re)
}
func (c MemberBlog) ListThemeImages(themeId string) revel.Result {
re := info.NewRe()
userId := c.GetUserId()
path := themeService.GetThemeAbsolutePath(userId, themeId) + "/images"
os.MkdirAll(path, 0755)
images := ListDir(path)
re.List = images
re.Ok = true
return c.RenderJSON(re)
}
func (c MemberBlog) DeleteThemeImage(themeId, filename string) revel.Result {
re := info.NewRe()
path := themeService.GetThemeAbsolutePath(c.GetUserId(), themeId) + "/images/" + filename
re.Ok = DeleteFile(path)
return c.RenderJSON(re)
}
// 上传主题图片
func (c MemberBlog) UploadThemeImage(themeId string) revel.Result {
re := c.uploadImage(themeId)
c.ViewArgs["fileUrlPath"] = re.Id
c.ViewArgs["resultCode"] = re.Code
c.ViewArgs["resultMsg"] = re.Msg
return c.RenderTemplate("file/blog_logo.html")
}
func (c MemberBlog) uploadImage(themeId string) (re info.Re) {
var fileId = ""
var resultCode = 0 // 1表示正常
var resultMsg = "内部错误" // 错误信息
var Ok = false
defer func() {
re.Id = fileId // 只是id, 没有其它信息
re.Code = resultCode
re.Msg = resultMsg
re.Ok = Ok
}()
file, handel, err := c.Request.FormFile("file")
if err != nil {
return re
}
defer file.Close()
// 生成上传路径
dir := themeService.GetThemeAbsolutePath(c.GetUserId(), themeId) + "/images"
err = os.MkdirAll(dir, 0755)
if err != nil {
return re
}
// 生成新的文件名
filename := handel.Filename
var ext string
_, ext = SplitFilename(filename)
if ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg" {
resultMsg = "不是图片"
return re
}
filename = filename
data, err := ioutil.ReadAll(file)
if err != nil {
LogJ(err)
return re
}
// > 2M?
if len(data) > 5*1024*1024 {
resultCode = 0
resultMsg = "图片大于2M"
return re
}
toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
LogJ(err)
return re
}
TransToGif(toPath, 0, true)
resultCode = 1
resultMsg = "上传成功!"
return re
}
//
// 使用主题
func (c MemberBlog) ActiveTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.ActiveTheme(c.GetUserId(), themeId)
return c.RenderJSON(re)
}
// 删除主题
func (c MemberBlog) DeleteTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.DeleteTheme(c.GetUserId(), themeId)
return c.RenderJSON(re)
}
// 管理员公开主题
func (c MemberBlog) PublicTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.PublicTheme(c.GetUserId(), themeId)
return c.RenderJSON(re)
}
// 导出
func (c MemberBlog) ExportTheme(themeId string) revel.Result {
re := info.NewRe()
var path string
re.Ok, path = themeService.ExportTheme(c.GetUserId(), themeId)
if !re.Ok {
return c.RenderText("error...")
}
fw, err := os.Open(path)
if err != nil {
return c.RenderText("error")
}
return c.RenderBinary(fw, GetFilename(path), revel.Attachment, time.Now()) // revel.Attachment
}
// 导入主题
func (c MemberBlog) ImportTheme() revel.Result {
re := info.NewRe()
file, handel, err := c.Request.FormFile("file")
if err != nil {
re.Msg = fmt.Sprintf("%v", err)
return c.RenderJSON(re)
}
defer file.Close()
// 生成上传路径
userId := c.GetUserId()
dir := revel.BasePath + "/public/upload/" + userId + "/tmp"
err = os.MkdirAll(dir, 0755)
if err != nil {
re.Msg = fmt.Sprintf("%v", err)
return c.RenderJSON(re)
}
// 生成新的文件名
filename := handel.Filename
var ext string
_, ext = SplitFilename(filename)
if ext != ".zip" {
re.Msg = "Please upload zip file"
return c.RenderJSON(re)
}
filename = filename
data, err := ioutil.ReadAll(file)
if err != nil {
return c.RenderJSON(re)
}
// > 10M?
if len(data) > 10*1024*1024 {
re.Msg = "File is big than 10M"
return c.RenderJSON(re)
}
toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
re.Msg = fmt.Sprintf("%v", err)
return c.RenderJSON(re)
}
// 上传好后, 增加之
re.Ok, re.Msg = themeService.ImportTheme(c.GetUserId(), toPath)
return c.RenderRe(re)
}
// 安装
func (c MemberBlog) InstallTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.InstallTheme(c.GetUserId(), themeId)
return c.RenderJSON(re)
}
// 新建主题
func (c MemberBlog) NewTheme() revel.Result {
_, themeId := themeService.NewTheme(c.GetUserId())
return c.Redirect("/member/blog/updateTheme?isNew=1&themeId=" + themeId)
}
//-----------
//
func (c MemberBlog) SetUserBlogBase(userBlog info.UserBlogBase) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpdateUserBlogBase(c.GetUserId(), userBlog)
return c.RenderJSON(re)
}
func (c MemberBlog) SetUserBlogComment(userBlog info.UserBlogComment) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpdateUserBlogComment(c.GetUserId(), userBlog)
return c.RenderJSON(re)
}
func (c MemberBlog) SetUserBlogStyle(userBlog info.UserBlogStyle) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpdateUserBlogStyle(c.GetUserId(), userBlog)
return c.RenderJSON(re)
}
func (c MemberBlog) SetUserBlogPaging(perPageSize int, sortField string, isAsc bool) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = blogService.UpdateUserBlogPaging(c.GetUserId(), perPageSize, sortField, isAsc)
return c.RenderRe(re)
}

View File

@@ -0,0 +1,58 @@
package member
import (
"github.com/leanote/leanote/app/info"
"github.com/revel/revel"
)
// 分组管理
type MemberGroup struct {
MemberBaseController
}
// 首页, 显示所有分组和用户
func (c MemberGroup) Index() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.ViewArgs["title"] = c.Message("My Group")
c.ViewArgs["groups"] = groupService.GetGroupsAndUsers(c.GetUserId())
return c.RenderTemplate("member/group/index.html")
}
// 添加分组
func (c MemberGroup) AddGroup(title string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = groupService.AddGroup(c.GetUserId(), title)
return c.RenderJSON(re)
}
func (c MemberGroup) UpdateGroupTitle(groupId, title string) revel.Result {
re := info.NewRe()
re.Ok = groupService.UpdateGroupTitle(c.GetUserId(), groupId, title)
return c.RenderJSON(re)
}
func (c MemberGroup) DeleteGroup(groupId string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = groupService.DeleteGroup(c.GetUserId(), groupId)
return c.RenderRe(re)
}
// 添加用户
func (c MemberGroup) AddUser(groupId, email string) revel.Result {
re := info.NewRe()
userInfo := userService.GetUserInfoByAny(email)
if userInfo.UserId == "" {
re.Msg = "userNotExists"
} else {
re.Ok, re.Msg = groupService.AddUser(c.GetUserId(), groupId, userInfo.UserId.Hex())
re.Item = userInfo
}
return c.RenderRe(re)
}
func (c MemberGroup) DeleteUser(groupId, userId string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = groupService.DeleteUser(c.GetUserId(), groupId, userId)
return c.RenderRe(re)
}

View File

@@ -0,0 +1,37 @@
package member
import (
"github.com/revel/revel"
)
// admin 首页
type MemberIndex struct {
MemberBaseController
}
// admin 主页
func (c MemberIndex) Index() revel.Result {
c.SetUserInfo()
c.ViewArgs["title"] = c.Message("Leanote Member Center")
c.ViewArgs["countNote"] = noteService.CountNote(c.GetUserId())
c.ViewArgs["countBlog"] = noteService.CountBlog(c.GetUserId())
c.SetLocale()
return c.RenderTemplate("member/index.html")
}
// 模板
func (c MemberIndex) T(t string) revel.Result {
c.ViewArgs["str"] = configService.GlobalStringConfigs
c.ViewArgs["arr"] = configService.GlobalArrayConfigs
c.ViewArgs["map"] = configService.GlobalMapConfigs
c.ViewArgs["arrMap"] = configService.GlobalArrMapConfigs
return c.RenderTemplate("admin/" + t + ".html")
}
func (c MemberIndex) GetView(view string) revel.Result {
return c.RenderTemplate("admin/" + view)
}

View File

@@ -0,0 +1,40 @@
package member
import (
"github.com/revel/revel"
)
// 帐户信息
type MemberUser struct {
MemberBaseController
}
func (c MemberUser) Username() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.ViewArgs["title"] = c.Message("Username")
return c.RenderTemplate("member/user/username.html")
}
func (c MemberUser) Email() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.ViewArgs["title"] = c.Message("Email")
return c.RenderTemplate("member/user/email.html")
}
func (c MemberUser) Password() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.ViewArgs["title"] = c.Message("Password")
return c.RenderTemplate("member/user/password.html")
}
func (c MemberUser) Avatar() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.ViewArgs["title"] = c.Message("Avatar")
c.ViewArgs["globalConfigs"] = configService.GetGlobalConfigForUser()
return c.RenderTemplate("member/user/avatar.html")
}

View File

@@ -0,0 +1,135 @@
package member
import (
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/service"
// . "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
// "strings"
)
var userService *service.UserService
var groupService *service.GroupService
var noteService *service.NoteService
var trashService *service.TrashService
var notebookService *service.NotebookService
var noteContentHistoryService *service.NoteContentHistoryService
var authService *service.AuthService
var shareService *service.ShareService
var blogService *service.BlogService
var tagService *service.TagService
var pwdService *service.PwdService
var tokenService *service.TokenService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
var noteImageService *service.NoteImageService
var fileService *service.FileService
var attachService *service.AttachService
var configService *service.ConfigService
var emailService *service.EmailService
var upgradeService *service.UpgradeService
var themeService *service.ThemeService
// 拦截器
// 不需要拦截的url
// Index 除了Note之外都不需要
var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": true,
"Login": true,
"DoLogin": true,
"Logout": true,
"Register": true,
"DoRegister": true,
"FindPasswword": true,
"DoFindPassword": true,
"FindPassword2": true,
"FindPasswordUpdate": true,
"Suggestion": true,
},
"Blog": map[string]bool{"Index": true,
"View": true,
"AboutMe": true,
"SearchBlog": true,
},
// 用户的激活与修改邮箱都不需要登录, 通过链接地址
"User": map[string]bool{"UpdateEmail": true,
"ActiveEmail": true,
},
"Oauth": map[string]bool{"GithubCallback": true},
"File": map[string]bool{"OutputImage": true, "OutputFile": true},
"Attach": map[string]bool{"Download": true, "DownloadAll": true},
}
func needValidate(controller, method string) bool {
// 在里面
if v, ok := commonUrl[controller]; ok {
// 在commonUrl里
if _, ok2 := v[method]; ok2 {
return false
}
return true
} else {
// controller不在这里的, 肯定要验证
return true
}
}
func AuthInterceptor(c *revel.Controller) revel.Result {
// 全部变成首字大写
/*
var controller = strings.Title(c.Name)
var method = strings.Title(c.MethodName)
// 是否需要验证?
if !needValidate(controller, method) {
return nil
}
*/
// 验证是否已登录
// 必须是管理员
if _, ok := c.Session["Username"]; ok {
return nil // 已登录
}
// 没有登录, 判断是否是ajax操作
if c.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
re := info.NewRe()
re.Msg = "NOTLOGIN"
return c.RenderJSON(re)
}
return c.Redirect("/login")
}
// 最外层init.go调用
// 获取service, 单例
func InitService() {
notebookService = service.NotebookS
noteService = service.NoteS
noteContentHistoryService = service.NoteContentHistoryS
trashService = service.TrashS
shareService = service.ShareS
userService = service.UserS
groupService = service.GroupS
tagService = service.TagS
blogService = service.BlogS
tokenService = service.TokenS
noteImageService = service.NoteImageS
fileService = service.FileS
albumService = service.AlbumS
attachService = service.AttachS
pwdService = service.PwdS
suggestionService = service.SuggestionS
authService = service.AuthS
configService = service.ConfigS
emailService = service.EmailS
upgradeService = service.UpgradeS
themeService = service.ThemeS
}
func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &MemberIndex{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &MemberUser{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &MemberBlog{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &MemberGroup{})
revel.OnAppStart(func() {
})
}

View File

@@ -2,9 +2,11 @@ package db
import (
"fmt"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"github.com/revel/revel"
"strings"
)
// Init mgo and the common DAO
@@ -23,9 +25,12 @@ var ShareNotebooks *mgo.Collection
var HasShareNotes *mgo.Collection
var Blogs *mgo.Collection
var Users *mgo.Collection
var Groups *mgo.Collection
var GroupUsers *mgo.Collection
var Tags *mgo.Collection
var TagNotes *mgo.Collection
var NoteTags *mgo.Collection
var TagCounts *mgo.Collection
var UserBlogs *mgo.Collection
@@ -36,14 +41,48 @@ var Suggestions *mgo.Collection
// Album & file(image)
var Albums *mgo.Collection
var Files *mgo.Collection
var Attachs *mgo.Collection
var NoteImages *mgo.Collection
var Configs *mgo.Collection
var EmailLogs *mgo.Collection
// blog
var BlogLikes *mgo.Collection
var BlogComments *mgo.Collection
var Reports *mgo.Collection
var BlogSingles *mgo.Collection
var Themes *mgo.Collection
// session
var Sessions *mgo.Collection
// 初始化时连接数据库
func Init() {
var url string
var ok bool
config := revel.Config;
url, ok = config.String("db.url")
dbname, _ := config.String("db.dbname")
func Init(url, dbname string) {
ok := true
config := revel.Config
if url == "" {
url, ok = config.String("db.url")
if !ok {
url, ok = config.String("db.urlEnv")
if ok {
Log("get db conf from urlEnv: " + url)
}
} else {
Log("get db conf from db.url: " + url)
}
if ok {
// get dbname from urlEnv
urls := strings.Split(url, "/")
dbname = urls[len(urls)-1]
}
}
if dbname == "" {
dbname, _ = config.String("db.dbname")
}
// get db config from host, port, username, password
if !ok {
host, _ := revel.Config.String("db.host")
port, _ := revel.Config.String("db.port")
@@ -53,9 +92,10 @@ func Init() {
if username == "" || password == "" {
usernameAndPassword = ""
}
url = "mongodb://" + usernameAndPassword + host + ":" + port + "/" + dbname
url = "mongodb://" + usernameAndPassword + host + ":" + port + "/" + dbname
}
Log(url)
// [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
// mongodb://myuser:mypass@localhost:40001,otherhost:40001/mydb
var err error
@@ -69,47 +109,63 @@ func Init() {
// notebook
Notebooks = Session.DB(dbname).C("notebooks")
// notes
Notes = Session.DB(dbname).C("notes")
// noteContents
NoteContents = Session.DB(dbname).C("note_contents")
NoteContentHistories = Session.DB(dbname).C("note_content_histories")
// share
ShareNotes = Session.DB(dbname).C("share_notes")
ShareNotebooks = Session.DB(dbname).C("share_notebooks")
HasShareNotes = Session.DB(dbname).C("has_share_notes")
// user
Users = Session.DB(dbname).C("users")
// group
Groups = Session.DB(dbname).C("groups")
GroupUsers = Session.DB(dbname).C("group_users")
// blog
Blogs = Session.DB(dbname).C("blogs")
// tag
Tags = Session.DB(dbname).C("tags")
TagNotes = Session.DB(dbname).C("tag_notes")
NoteTags = Session.DB(dbname).C("note_tags")
TagCounts = Session.DB(dbname).C("tag_count")
// blog
UserBlogs = Session.DB(dbname).C("user_blogs")
BlogSingles = Session.DB(dbname).C("blog_singles")
Themes = Session.DB(dbname).C("themes")
// find password
Tokens = Session.DB(dbname).C("tokens")
// Suggestion
Suggestions = Session.DB(dbname).C("suggestions")
// Album & file
Albums = Session.DB(dbname).C("albums")
Files = Session.DB(dbname).C("files")
}
Attachs = Session.DB(dbname).C("attachs")
func init() {
revel.OnAppStart(func() {
Init()
})
NoteImages = Session.DB(dbname).C("note_images")
Configs = Session.DB(dbname).C("configs")
EmailLogs = Session.DB(dbname).C("email_logs")
// 社交
BlogLikes = Session.DB(dbname).C("blog_likes")
BlogComments = Session.DB(dbname).C("blog_comments")
// 举报
Reports = Session.DB(dbname).C("reports")
// session
Sessions = Session.DB(dbname).C("sessions")
}
func close() {
@@ -151,22 +207,29 @@ func UpdateByIdAndUserId2(collection *mgo.Collection, id, userId bson.ObjectId,
return Err(err)
}
func UpdateByIdAndUserIdField(collection *mgo.Collection, id, userId, field string, value interface{}) bool {
return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": bson.M{field:value}})
return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": bson.M{field: value}})
}
func UpdateByIdAndUserIdMap(collection *mgo.Collection, id, userId string, v bson.M) bool {
return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": v})
}
func UpdateByIdAndUserIdField2(collection *mgo.Collection, id, userId bson.ObjectId, field string, value interface{}) bool {
return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": bson.M{field:value}})
return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": bson.M{field: value}})
}
func UpdateByIdAndUserIdMap2(collection *mgo.Collection, id, userId bson.ObjectId, v bson.M) bool {
return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": v})
}
//
//
func UpdateByQField(collection *mgo.Collection, q interface{}, field string, value interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": bson.M{field: value}})
return Err(err)
}
func UpdateByQI(collection *mgo.Collection, q interface{}, v interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": v})
return Err(err)
}
// 查询条件和值
func UpdateByQMap(collection *mgo.Collection, q interface{}, v interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": v})
@@ -232,6 +295,7 @@ func GetByQWithFields(collection *mgo.Collection, q bson.M, fields []string, i i
}
collection.Find(q).Select(selector).One(i)
}
// 查询某些字段, q是查询条件, fields是字段名列表
func ListByQWithFields(collection *mgo.Collection, q bson.M, fields []string, i interface{}) {
selector := make(bson.M, len(fields))
@@ -247,6 +311,11 @@ func GetByIdAndUserId2(collection *mgo.Collection, id, userId bson.ObjectId, i i
collection.Find(GetIdAndUserIdBsonQ(id, userId)).One(i)
}
// 按field去重
func Distinct(collection *mgo.Collection, q bson.M, field string, i interface{}) {
collection.Find(q).Distinct(field, i)
}
//----------------------
func Count(collection *mgo.Collection, q interface{}) int {
@@ -280,9 +349,26 @@ func Err(err error) bool {
fmt.Println(err)
// 删除时, 查找
if err.Error() == "not found" {
return true;
return true
}
return false
}
return true
}
}
// 检查mognodb是否lost connection
// 每个请求之前都要检查!!
func CheckMongoSessionLost() {
// fmt.Println("检查CheckMongoSessionLostErr")
err := Session.Ping()
if err != nil {
Log("Lost connection to db!")
Session.Refresh()
err = Session.Ping()
if err == nil {
Log("Reconnect to db successful.")
} else {
Log("重连失败!!!! 警告")
}
}
}

View File

@@ -1,72 +0,0 @@
package main
import (
"fmt"
"os"
"bufio"
"strings"
"encoding/json"
)
// convert revel msg to js msg
var msgBasePath = "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/messages/"
var targetBasePath = "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/public/js/i18n/"
func parse(filename string) {
file, err := os.Open(msgBasePath + filename)
reader := bufio.NewReader(file)
msg := map[string]string{}
if err != nil {
fmt.Println(err)
return
}
for true {
line, _, err := reader.ReadLine()
if err != nil {
break
}
if len(line) == 0 {
continue
}
// 对每一行进行处理
if line[0] == '#' || line[1] == '#' {
continue;
}
lineStr := string(line)
// 找到第一个=位置
pos := strings.Index(lineStr, "=")
if pos < 0 {
continue;
}
key := string(line[0:pos])
value := string(line[pos+1:])
// fmt.Println(lineStr)
// fmt.Println(value)
msg[key] = value
}
// JSON
b, _ := json.Marshal(msg)
str := string(b)
fmt.Println(str);
targetName := targetBasePath + filename + ".js"
file2, err2 := os.OpenFile(targetName, os.O_RDWR|os.O_CREATE, 0644)
if err2 != nil {
file2, err2 = os.Create(targetName)
}
file2.WriteString("var MSG = " + str + ";")
}
// 生成js的i18n文件
func main() {
parse("msg.en")
parse("msg.zh")
}

119
app/info/Api.go Normal file
View File

@@ -0,0 +1,119 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
//---------
// 数据结构
//---------
type NoteFile struct {
FileId string // 服务器端Id
LocalFileId string // 客户端Id
Type string // images/png, doc, xls, 根据fileName确定
Title string
HasBody bool // 传过来的值是否要更新内容
IsAttach bool // 是否是附件, 不是附件就是图片
}
type ApiNote struct {
NoteId string
NotebookId string
UserId string
Title string
Desc string
// ImgSrc string
Tags []string
Abstract string
Content string
IsMarkdown bool
// FromUserId string // 为共享而新建
IsBlog bool // 是否是blog, 更新note不需要修改, 添加note时才有可能用到, 此时需要判断notebook是否设为Blog
IsTrash bool
IsDeleted bool
Usn int
Files []NoteFile
CreatedTime time.Time
UpdatedTime time.Time
PublicTime time.Time
}
// 内容
type ApiNoteContent struct {
NoteId bson.ObjectId `bson:"_id,omitempty"`
UserId bson.ObjectId `bson:"UserId"`
Content string `Content`
// CreatedTime time.Time `CreatedTime`
// UpdatedTime time.Time `UpdatedTime`
}
// 转换
func NoteToApiNote(note Note, files []NoteFile) ApiNote {
apiNote := ApiNote{}
return apiNote
}
//----------
// 用户信息
//----------
type ApiUser struct {
UserId string
Username string
Email string
Verified bool
Logo string
}
//----------
// Notebook
//----------
type ApiNotebook struct {
NotebookId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"`
ParentNotebookId bson.ObjectId `bson:"ParentNotebookId,omitempty"` // 上级
Seq int `Seq` // 排序
Title string `Title` // 标题
UrlTitle string `UrlTitle` // Url标题 2014/11.11加
IsBlog bool `IsBlog,omitempty` // 是否是Blog 2013/12/29 新加
CreatedTime time.Time `CreatedTime,omitempty`
UpdatedTime time.Time `UpdatedTime,omitempty`
Usn int `Usn` // UpdateSequenceNum
IsDeleted bool `IsDeleted`
}
//---------
// api 返回
//---------
// 一般返回
type ApiRe struct {
Ok bool
Msg string
}
func NewApiRe() ApiRe {
return ApiRe{Ok: false}
}
// auth
type AuthOk struct {
Ok bool
Token string
UserId bson.ObjectId
Email string
Username string
}
// 供notebook, note, tag更新的返回数据用
type ReUpdate struct {
Ok bool
Msg string
Usn int
}
func NewReUpdate() ReUpdate {
return ReUpdate{Ok: false}
}

21
app/info/AttachInfo.go Normal file
View File

@@ -0,0 +1,21 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// Attach belongs to note
type Attach struct {
AttachId bson.ObjectId `bson:"_id,omitempty"` //
NoteId bson.ObjectId `bson:"NoteId"` //
UploadUserId bson.ObjectId `bson:"UploadUserId"` // 可以不是note owner, 协作者userId
Name string `Name` // file name, md5, such as 13232312.doc
Title string `Title` // raw file name
Size int64 `Size` // file size (byte)
Type string `Type` // file type, "doc" = word
Path string `Path` // the file path such as: files/userId/attachs/adfadf.doc
CreatedTime time.Time `CreatedTime`
// FromFileId bson.ObjectId `bson:"FromFileId,omitempty"` // copy from fileId, for collaboration
}

58
app/info/BlogCustom.go Normal file
View File

@@ -0,0 +1,58 @@
package info
import (
"time"
)
// 仅仅为了博客的主题
type BlogInfoCustom struct {
UserId string
Username string
UserLogo string
Title string
SubTitle string
Logo string
OpenComment bool
CommentType string
ThemeId string
SubDomain string
Domain string
}
type Post struct {
NoteId string
Title string
UrlTitle string
ImgSrc string
CreatedTime time.Time
UpdatedTime time.Time
PublicTime time.Time
Desc string
Abstract string
Content string
Tags []string
CommentNum int
ReadNum int
LikeNum int
IsMarkdown bool
}
// 归档
type ArchiveMonth struct {
Month int
Posts []*Post
}
type Archive struct {
Year int
MonthAchives []ArchiveMonth
Posts []*Post
}
type Cate struct {
CateId string
ParentCateId string
Title string
UrlTitle string
Children []*Cate
}

View File

@@ -2,42 +2,127 @@ package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 只为blog, 不为note
type BlogItem struct {
Note
Content string // 可能是content的一部分, 截取. 点击more后就是整个信息了
HasMore bool // 是否是否还有
Abstract string
Content string // 可能是content的一部分, 截取. 点击more后就是整个信息了
HasMore bool // 是否是否还有
User User // 用户信息
}
type UserBlogBase struct {
Logo string `Logo`
Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题
AboutMe string `AboutMe` // 关于我
Logo string `Logo`
Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题
// AboutMe string `AboutMe` // 关于我
}
type UserBlogComment struct {
CanComment bool `CanComment` // 是否可以评论
DisqusId string `DisqusId`
CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId`
}
type UserBlogStyle struct {
Style string `Style` // 风格
Style string `Style` // 风格
Css string `Css` // 自定义css
}
// 每个用户一份博客设置信息
type UserBlog struct {
UserId bson.ObjectId `bson:"_id"` // 谁的
Logo string `Logo`
Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题
AboutMe string `AboutMe` // 关于我
CanComment bool `CanComment` // 是否可以评论
DisqusId string `DisqusId`
Style string `Style` // 风格
}
UserId bson.ObjectId `bson:"_id"` // 谁的
Logo string `Logo`
Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题
AboutMe string `AboutMe` // 关于我, 弃用
CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId`
Style string `Style` // 风格
Css string `Css` // 自定义css
ThemeId bson.ObjectId `ThemeId,omitempty` // 主题Id
ThemePath string `bson:"ThemePath" json:"-"` // 不存值, 从Theme中获取, 相对路径 public/
CateIds []string `CateIds,omitempty` // 分类Id, 排序好的
Singles []map[string]string `Singles,omitempty` // 单页, 排序好的, map包含: ["Title"], ["SingleId"]
PerPageSize int `PerPageSize,omitempty`
SortField string `SortField` // 排序字段
IsAsc bool `IsAsc,omitempty` // 排序类型, 降序, 升序, 默认是false, 表示降序
SubDomain string `SubDomain` // 二级域名
Domain string `Domain` // 自定义域名
}
// 博客统计信息
type BlogStat struct {
NoteId bson.ObjectId `bson:"_id,omitempty"`
ReadNum int `ReadNum,omitempty` // 阅读次数 2014/9/28
LikeNum int `LikeNum,omitempty` // 点赞次数 2014/9/28
CommentNum int `CommentNum,omitempty` // 评论次数 2014/9/28
}
// 单页
type BlogSingle struct {
SingleId bson.ObjectId `bson:"_id,omitempty"`
UserId bson.ObjectId `UserId`
Title string `Title`
UrlTitle string `UrlTitle` // 2014/11/11
Content string `Content`
UpdatedTime time.Time `UpdatedTime`
CreatedTime time.Time `CreatedTime`
}
//------------------------
// 社交功能, 点赞, 分享, 评论
// 点赞记录
type BlogLike struct {
LikeId bson.ObjectId `bson:"_id,omitempty"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId`
CreatedTime time.Time `CreatedTime`
}
// 评论
type BlogComment struct {
CommentId bson.ObjectId `bson:"_id,omitempty"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId` // UserId回复ToUserId
Content string `Content` // 评论内容
ToCommentId bson.ObjectId `ToCommendId,omitempty` // 对某条评论进行回复
ToUserId bson.ObjectId `ToUserId,omitempty` // 为空表示直接评论, 不回空表示回复某人
LikeNum int `LikeNum` // 点赞次数, 评论也可以点赞
LikeUserIds []string `LikeUserIds` // 点赞的用户ids
CreatedTime time.Time `CreatedTime`
}
type BlogCommentPublic struct {
BlogComment
IsILikeIt bool
}
type BlogUrls struct {
IndexUrl string
CateUrl string
SearchUrl string
SingleUrl string
PostUrl string
ArchiveUrl string
TagsUrl string
TagPostsUrl string
}

25
app/info/Configinfo.go Normal file
View File

@@ -0,0 +1,25 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 配置, 每一个配置一行记录
type Config struct {
ConfigId bson.ObjectId `bson:"_id"`
UserId bson.ObjectId `UserId`
Key string `Key`
ValueStr string `ValueStr,omitempty` // "1"
ValueArr []string `ValueArr,omitempty` // ["1","b","c"]
ValueMap map[string]string `ValueMap,omitempty` // {"a":"bb", "CC":"xx"}
ValueArrMap []map[string]string `ValueArrMap,omitempty` // [{"a":"B"}, {}, {}]
IsArr bool `IsArr` // 是否是数组
IsMap bool `IsMap` // 是否是Map
IsArrMap bool `IsArrMap` // 是否是数组Map
// StringConfigs map[string]string `StringConfigs` // key => value
// ArrayConfigs map[string][]string `ArrayConfigs` // key => []value
UpdatedTime time.Time `UpdatedTime`
}

19
app/info/EmailLogInfo.go Normal file
View File

@@ -0,0 +1,19 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 发送邮件
type EmailLog struct {
LogId bson.ObjectId `bson:"_id"`
Email string `Email` // 发送者
Subject string `Subject` // 主题
Body string `Body` // 内容
Msg string `Msg` // 发送失败信息
Ok bool `Ok` // 发送是否成功
CreatedTime time.Time `CreatedTime`
}

View File

@@ -11,9 +11,11 @@ type File struct {
AlbumId bson.ObjectId `bson:"AlbumId"`
Name string `Name` // file name
Title string `Title` // file name or user defined for search
Size int64 `Size` // file size (byte)
Type string `Type` // file type, such as image/jpg
Path string `Path` // the file path, based on /upload
Size int64 `Size` // file size (byte)
Type string `Type` // file type, "" = image, "doc" = word
Path string `Path` // the file path
IsDefaultAlbum bool `IsDefaultAlbum`
CreatedTime time.Time `CreatedTime`
FromFileId bson.ObjectId `bson:"FromFileId,omitempty"` // copy from fileId, for collaboration
}

25
app/info/GroupInfo.go Normal file
View File

@@ -0,0 +1,25 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 分组
type Group struct {
GroupId bson.ObjectId `bson:"_id"` // 谁的
UserId bson.ObjectId `UserId` // 所有者Id
Title string `Title` // 标题
UserCount int `UserCount` // 用户数
CreatedTime time.Time `CreatedTime`
Users []User `Users,omitempty` // 分组下的用户, 不保存, 仅查看
}
// 分组好友
type GroupUser struct {
GroupUserId bson.ObjectId `bson:"_id"` // 谁的
GroupId bson.ObjectId `GroupId` // 分组
UserId bson.ObjectId `UserId` // 用户
CreatedTime time.Time `CreatedTime`
}

12
app/info/NoteImage.go Normal file
View File

@@ -0,0 +1,12 @@
package info
import (
"gopkg.in/mgo.v2/bson"
)
// 笔记内部图片
type NoteImage struct {
NoteImageId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
NoteId bson.ObjectId `bson:"NoteId"` // 笔记
ImageId bson.ObjectId `bson:"ImageId"` // 图片fileId
}

View File

@@ -15,18 +15,38 @@ type Note struct {
Title string `Title` // 标题
Desc string `Desc` // 描述, 非html
ImgSrc string `ImgSrc` // 图片, 第一张缩略图地址
Tags []string `Tags,omitempty`
IsTrash bool `IsTrash` // 是否是trash, 默认是false
Src string `Src,omitempty` // 来源, 2016/4/22
IsBlog bool `IsBlog,omitempty` // 是否设置成了blog 2013/12/29 新加
IsTop bool `IsTop,omitempty` // blog是否置顶
ImgSrc string `ImgSrc` // 图片, 第一张缩略图地址
Tags []string `Tags,omitempty`
IsTrash bool `IsTrash` // 是否是trash, 默认是false
IsBlog bool `IsBlog,omitempty` // 是否设置成了blog 2013/12/29 新加
UrlTitle string `UrlTitle,omitempty` // 博客的url标题, 为了更友好的url, 在UserId, UrlName下唯一
IsRecommend bool `IsRecommend,omitempty` // 是否为推荐博客 2014/9/24新加
IsTop bool `IsTop,omitempty` // blog是否置顶
HasSelfDefined bool `HasSelfDefined` // 是否已经自定义博客图片, desc, abstract
// 2014/9/28 添加评论社交功能
ReadNum int `ReadNum,omitempty` // 阅读次数 2014/9/28
LikeNum int `LikeNum,omitempty` // 点赞次数 2014/9/28
CommentNum int `CommentNum,omitempty` // 评论次数 2014/9/28
IsMarkdown bool `IsMarkdown` // 是否是markdown笔记, 默认是false
AttachNum int `AttachNum` // 2014/9/21, attachments num
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime`
UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了
RecommendTime time.Time `RecommendTime,omitempty` // 推荐时间
PublicTime time.Time `PublicTime,omitempty` // 发表时间, 公开为博客则设置
UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了
// 2015/1/15, 更新序号
Usn int `Usn` // UpdateSequenceNum
IsDeleted bool `IsDeleted` // 删除位
}
// 内容
@@ -54,11 +74,39 @@ type NoteAndContent struct {
// 每一个历史记录对象
type EachHistory struct {
UpdatedUserId bson.ObjectId `UpdatedUserId`
UpdatedTime time.Time `UpdatedTime`
Content string `Content`
UpdatedTime time.Time `UpdatedTime`
Content string `Content`
}
type NoteContentHistory struct {
NoteId bson.ObjectId `bson:"_id,omitempty"`
UserId bson.ObjectId `bson:"UserId"` // 所属者
Histories []EachHistory `Histories`
}
NoteId bson.ObjectId `bson:"_id,omitempty"`
UserId bson.ObjectId `bson:"UserId"` // 所属者
Histories []EachHistory `Histories`
}
// 为了NoteController接收参数
// 更新note或content
// 肯定会传userId(谁的), NoteId
// 会传Title, Content, Tags, 一种或几种
type NoteOrContent struct {
NotebookId string
NoteId string
UserId string
Title string
Desc string
Src string
ImgSrc string
Tags string
Content string
Abstract string
IsNew bool
IsMarkdown bool
FromUserId string // 为共享而新建
IsBlog bool // 是否是blog, 更新note不需要修改, 添加note时才有可能用到, 此时需要判断notebook是否设为Blog
}
// 分开的
type NoteAndContentSep struct {
NoteInfo Note
NoteContentInfo NoteContent
}

View File

@@ -13,15 +13,20 @@ type Notebook struct {
ParentNotebookId bson.ObjectId `bson:"ParentNotebookId,omitempty"` // 上级
Seq int `Seq` // 排序
Title string `Title` // 标题
UrlTitle string `UrlTitle` // Url标题 2014/11.11加
NumberNotes int `NumberNotes` // 笔记数
IsTrash bool `IsTrash,omitempty` // 是否是trash, 默认是false
IsBlog bool `IsBlog,omitempty` // 是否是Blog 2013/12/29 新加
CreatedTime time.Time `CreatedTime,omitempty`
UpdatedTime time.Time `UpdatedTime,omitempty`
// 2015/1/15, 更新序号
Usn int `Usn` // UpdateSequenceNum
IsDeleted bool `IsDeleted`
}
// 仅仅是为了返回前台
type SubNotebooks []Notebooks
type SubNotebooks []*Notebooks // 存地址, 为了生成tree
type Notebooks struct {
Notebook
Subs SubNotebooks // 子notebook 在数据库中是没有的
@@ -32,7 +37,7 @@ func (this SubNotebooks) Len() int {
return len(this)
}
func (this SubNotebooks) Less(i, j int) bool {
return this[i].Seq < this[j].Seq
return (*this[i]).Seq < (*this[j]).Seq
}
func (this SubNotebooks) Swap(i, j int) {
this[i], this[j] = this[j], this[i]

View File

@@ -1,18 +1,17 @@
package info
import (
)
import ()
// controller ajax返回
type Re struct {
Ok bool
Ok bool
Code int
Msg string
Id string
Msg string
Id string
List interface{}
Item interface{}
}
func NewRe() Re {
return Re{Ok: false}
}
}

19
app/info/ReportInfo.go Normal file
View File

@@ -0,0 +1,19 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 举报
type Report struct {
ReportId bson.ObjectId `bson:"_id"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId` // UserId回复ToUserId
Reason string `Reason` // 评论内容
CommentId bson.ObjectId `CommendId,omitempty` // 对某条评论进行回复
CreatedTime time.Time `CreatedTime`
}

21
app/info/SessionInfo.go Normal file
View File

@@ -0,0 +1,21 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// http://docs.mongodb.org/manual/tutorial/expire-data/
type Session struct {
Id bson.ObjectId `bson:"_id,omitempty"` // 没有意义
SessionId string `bson:"SessionId"` // SessionId
LoginTimes int `LoginTimes` // 登录错误时间
Captcha string `Captcha` // 验证码
UserId string `UserId` // API时有值UserId
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime` // 更新时间, expire这个时间会自动清空
}

View File

@@ -76,7 +76,9 @@ type SharingNotebookAndNotes struct {
type ShareNotebook struct {
ShareNotebookId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"`
ToUserId bson.ObjectId `bson:"ToUserId"`
ToUserId bson.ObjectId `bson:"ToUserId,omitempty"`
ToGroupId bson.ObjectId `bson:"ToGroupId,omitempty"` // 分享给的用户组
ToGroup Group `ToGroup,omitempty` // 仅仅为了显示, 不存储, 分组信息
NotebookId bson.ObjectId `bson:"NotebookId"`
Seq int `bson:"Seq"` // 排序
Perm int `bson:"Perm"` // 权限, 其下所有notes 0只读, 1可修改
@@ -134,7 +136,9 @@ type ShareNotebooksByUser struct {
type ShareNote struct {
ShareNoteId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"`
ToUserId bson.ObjectId `bson:"ToUserId"`
ToUserId bson.ObjectId `bson:"ToUserId,omitempty"`
ToGroupId bson.ObjectId `bson:"ToGroupId,omitempty"` // 分享给的用户组
ToGroup Group `ToGroup,omitempty` // 仅仅为了显示, 不存储, 分组信息
NoteId bson.ObjectId `bson:"NoteId"`
Perm int `bson:"Perm"` // 权限, 0只读, 1可修改
CreatedTime time.Time `CreatedTime`

View File

@@ -2,18 +2,55 @@ package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 这里主要是为了统计每个tag的note数目
// 暂时没用
/*
type TagNote struct {
TagId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"`
Tag string `Title` // 标题
NoteNum int `NoteNum` // note数目
}
*/
// 每个用户一条记录, 存储用户的所有tags
type Tag struct {
UserId bson.ObjectId `bson:"_id"`
Tags []string `Tags`
}
UserId bson.ObjectId `bson:"_id"`
Tags []string `Tags`
}
// v2 版标签
type NoteTag struct {
TagId bson.ObjectId `bson:"_id"`
UserId bson.ObjectId `UserId` // 谁的
Tag string `Tag` // UserId, Tag是唯一索引
Usn int `Usn` // Update Sequence Number
Count int `Count` // 笔记数
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime`
IsDeleted bool `IsDeleted` // 删除位
}
type TagCount struct {
TagCountId bson.ObjectId `bson:"_id,omitempty"`
UserId bson.ObjectId `UserId` // 谁的
Tag string `Tag`
IsBlog bool `IsBlog` // 是否是博客的tag统计
Count int `Count` // 统计数量
}
/*
type TagsCounts []TagCount
func (this TagsCounts) Len() int {
return len(this)
}
func (this TagsCounts) Less(i, j int) bool {
return this[i].Count > this[j].Count
}
func (this TagsCounts) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
}
*/

26
app/info/ThemeInfo.go Normal file
View File

@@ -0,0 +1,26 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 主题, 每个用户有多个主题, 这里面有主题的配置信息
// 模板, css, js, images, 都在路径Path下
type Theme struct {
ThemeId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `UserId`
Name string `Name`
Version string `Version`
Author string `Author`
AuthorUrl string `AuthorUrl`
Path string `Path` // 文件夹路径, public/upload/54d7620d99c37b030600002c/themes/54d867c799c37b533e000001
Info map[string]interface{} `Info` // 所有信息
IsActive bool `IsActive` // 是否在用
IsDefault bool `IsDefault` // leanote默认主题, 如果用户修改了默认主题, 则先copy之. 也是admin用户的主题
Style string `Style,omitempty` // 之前的, 只有default的用户才有blog_default, blog_daqi, blog_left_fixed
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime`
}

View File

@@ -18,7 +18,7 @@ const (
// 过期时间
const (
PwdOverHours = 2.0
PwdOverHours = 2.0
ActiveEmailOverHours = 48.0
UpdateEmailOverHours = 2.0
)
@@ -29,4 +29,4 @@ type Token struct {
Token string `Token`
Type int `Type`
CreatedTime time.Time `CreatedTime`
}
}

View File

@@ -20,16 +20,72 @@ type User struct {
Pwd string `bson:"Pwd" json:"-"`
CreatedTime time.Time `CreatedTime`
Logo string `Logo` // 9-24
// 主题
Theme string `Theme`
// 用户配置
NotebookWidth int `NotebookWidth` // 笔记本宽度
NoteListWidth int `NoteListWidth` // 笔记列表宽度
MdEditorWidth int `MdEditorWidth` // markdown 左侧编辑器宽度
LeftIsMin bool `LeftIsMin` // 左侧是否是隐藏的, 默认是打开的
// 这里 第三方登录
ThirdUserId string `ThirdUserId` // 用户Id, 在第三方中唯一可识别
ThirdUsername string `ThirdUsername` // 第三方中username, 为了显示
ThirdType int `ThirdType` // 第三方类型
// 用户的帐户类型
ImageNum int `bson:"ImageNum" json:"-"` // 图片数量
ImageSize int `bson:"ImageSize" json:"-"` // 图片大小
AttachNum int `bson:"AttachNum" json:"-"` // 附件数量
AttachSize int `bson:"AttachSize" json:"-"` // 附件大小
FromUserId bson.ObjectId `FromUserId,omitempty` // 邀请的用户
AccountType string `bson:"AccountType" json:"-"` // normal(为空), premium
AccountStartTime time.Time `bson:"AccountStartTime" json:"-"` // 开始日期
AccountEndTime time.Time `bson:"AccountEndTime" json:"-"` // 结束日期
// 阈值
MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量
MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小
MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量
MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小
MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小
// 2015/1/15, 更新序号
Usn int `Usn` // UpdateSequenceNum , 全局的
FullSyncBefore time.Time `bson:"FullSyncBefore"` // 需要全量同步的时间, 如果 > 客户端的LastSyncTime, 则需要全量更新
}
type UserAccount struct {
AccountType string `bson:"AccountType" json:"-"` // normal(为空), premium
AccountStartTime time.Time `bson:"AccountStartTime" json:"-"` // 开始日期
AccountEndTime time.Time `bson:"AccountEndTime" json:"-"` // 结束日期
// 阈值
MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量
MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小
MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量
MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小
MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小
}
// note主页需要
type UserAndBlogUrl struct {
User
BlogUrl string `BlogUrl`
PostUrl string `PostUrl`
}
// 用户与博客信息结合, 公开
type UserAndBlog struct {
UserId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
Email string `Email` // 全是小写
Username string `Username` // 不区分大小写, 全是小写
Logo string `Logo`
BlogTitle string `BlogTitle` // 博客标题
BlogLogo string `BlogLogo` // 博客Logo
BlogUrl string `BlogUrl` // 博客链接, 主页
BlogUrls // 各个页面
}

View File

@@ -1,13 +1,22 @@
package info
import (
"math"
)
// 分页数据
type Page struct {
CurPage int // 当前页码
TotalPage int // 总页
Count int // 总记录数
List interface{}
}
CurPage int // 当前页码
TotalPage int // 总页
PerPageSize int
Count int // 总记录数
List interface{}
}
func NewPage(page, perPageSize, count int, list interface{}) Page {
totalPage := 0
if count > 0 {
totalPage = int(math.Ceil(float64(count) / float64(perPageSize)))
}
return Page{page, totalPage, perPageSize, count, list}
}

View File

@@ -1,87 +1,350 @@
package app
import (
"github.com/revel/revel"
"encoding/json"
"fmt"
"github.com/leanote/leanote/app/controllers"
"github.com/leanote/leanote/app/controllers/admin"
"github.com/leanote/leanote/app/controllers/api"
"github.com/leanote/leanote/app/controllers/member"
"github.com/leanote/leanote/app/db"
. "github.com/leanote/leanote/app/lea"
_ "github.com/leanote/leanote/app/lea/binder"
"reflect"
"fmt"
"github.com/leanote/leanote/app/lea/i18n"
"github.com/leanote/leanote/app/lea/route"
"github.com/leanote/leanote/app/service"
"github.com/revel/revel"
"html/template"
"math"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
func init() {
// Filters is the default set of global filters.
revel.Filters = []revel.Filter{
revel.PanicFilter, // Recover from panics and display an error page instead.
revel.RouterFilter, // Use the routing table to select the right Action
revel.PanicFilter, // Recover from panics and display an error page instead.
route.RouterFilter,
// revel.RouterFilter, // Use the routing table to select the right Action
// AuthFilter, // Invoke the action.
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
revel.ParamsFilter, // Parse parameters into Controller.Params.
revel.SessionFilter, // Restore and write the session cookie.
// session.SessionFilter, // leanote memcache session life
revel.FlashFilter, // Restore and write the flash cookie.
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
revel.I18nFilter, // Resolve the requested language
revel.InterceptorFilter, // Run interceptors around the action.
revel.CompressFilter, // Compress the result.
revel.ActionInvoker, // Invoke the action.
// 使用SessionFilter标准版从cookie中得到sessionID, 然后通过MssessionFilter从Memcache中得到
// session, 之后MSessionFilter将session只存sessionID然后返回给SessionFilter返回到web
// session.SessionFilter, // leanote session
// session.MSessionFilter, // leanote memcache session
revel.FlashFilter, // Restore and write the flash cookie.
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
// revel.I18nFilter, // Resolve the requested language
i18n.I18nFilter, // Resolve the requested language by leanote
revel.InterceptorFilter, // Run interceptors around the action.
revel.CompressFilter, // Compress the result.
revel.ActionInvoker, // Invoke the action.
}
revel.TemplateFuncs["raw"] = func(str string) template.HTML {
return template.HTML(str)
}
revel.TemplateFuncs["add"] = func(i int) template.HTML {
i = i + 1;
return template.HTML(fmt.Sprintf("%v", i))
revel.TemplateFuncs["trim"] = func(str string) string {
str = strings.Trim(str, " ")
str = strings.Trim(str, " ")
str = strings.Trim(str, "\n")
str = strings.Trim(str, "&nbsp;")
// 以下两个空格不一样
str = strings.Trim(str, " ")
str = strings.Trim(str, " ")
return str
}
revel.TemplateFuncs["add"] = func(i int) string {
i = i + 1
return fmt.Sprintf("%v", i)
}
revel.TemplateFuncs["sub"] = func(i int) int {
i = i - 1
return i
}
// 增加或减少
revel.TemplateFuncs["incr"] = func(n, i int) int {
n = n + i
return n
}
revel.TemplateFuncs["join"] = func(arr []string) template.HTML {
if arr == nil {
return template.HTML("")
}
return template.HTML(strings.Join(arr, ","))
}
revel.TemplateFuncs["concat"] = func(s1, s2 string) template.HTML {
return template.HTML(s1 + s2)
}
revel.TemplateFuncs["concatStr"] = func(strs ...string) string {
str := ""
for _, s := range strs {
str += s
}
return str
}
revel.TemplateFuncs["decodeUrlValue"] = func(i string) string {
v, _ := url.ParseQuery("a=" + i)
return v.Get("a")
}
revel.TemplateFuncs["json"] = func(i interface{}) string {
b, _ := json.Marshal(i)
return string(b)
}
revel.TemplateFuncs["jsonJs"] = func(i interface{}) template.JS {
b, _ := json.Marshal(i)
return template.JS(string(b))
}
revel.TemplateFuncs["datetime"] = func(t time.Time) template.HTML {
return template.HTML(t.Format("2006-01-02 15:04:05"))
}
revel.TemplateFuncs["dateFormat"] = func(t time.Time, format string) template.HTML {
return template.HTML(t.Format(format))
}
revel.TemplateFuncs["unixDatetime"] = func(unixSec string) template.HTML {
sec, _ := strconv.Atoi(unixSec)
t := time.Unix(int64(sec), 0)
return template.HTML(t.Format("2006-01-02 15:04:05"))
}
// interface是否有该字段
revel.TemplateFuncs["has"] = func(i interface{}, key string) bool {
t := reflect.TypeOf(i)
t := reflect.TypeOf(i)
_, ok := t.FieldByName(key)
return ok
}
// tags
revel.TemplateFuncs["blogTags"] = func(tags []string) template.HTML {
// 2014/12/30 标签添加链接
revel.TemplateFuncs["blogTags"] = func(renderArgs map[string]interface{}, tags []string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
}
// TODO 这里判断语言, 从语言包中拿
tagMap := map[string]string{"red": "红色", "yellow": "黄色", "blue": "蓝色", "green": "绿色"}
locale, _ := renderArgs[revel.CurrentLocaleViewArg].(string)
tagStr := ""
lenTags := len(tags)
tagPostUrl, _ := renderArgs["tagPostsUrl"].(string)
for i, tag := range tags {
if text, ok := tagMap[tag]; ok {
tagStr += text
} else {
tagStr += tag
str := revel.Message(locale, tag)
var classes = "label"
if strings.HasPrefix(str, "???") {
str = tag
}
if i != lenTags - 1 {
tagStr += ","
if InArray([]string{"red", "blue", "yellow", "green"}, tag) {
classes += " label-" + tag
} else {
classes += " label-default"
}
classes += " label-post"
var url = tagPostUrl + "/" + tag
tagStr += "<a class=\"" + classes + "\" href=\"" + url + "\">" + str + "</a>"
if i != lenTags-1 {
tagStr += " "
}
}
return template.HTML(tagStr)
}
// pagination
revel.TemplateFuncs["page"] = func(userId, notebookId string, page, pageSize, count int) template.HTML {
if count == 0 {
return "";
revel.TemplateFuncs["blogTagsForExport"] = func(renderArgs map[string]interface{}, tags []string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
}
totalPage := int(math.Ceil(float64(count)/float64(pageSize)))
tagStr := ""
lenTags := len(tags)
for i, tag := range tags {
str := tag
var classes = "label"
if InArray([]string{"red", "blue", "yellow", "green"}, tag) {
classes += " label-" + tag
} else {
classes += " label-default"
}
classes += " label-post"
tagStr += "<span class=\"" + classes + "\" >" + str + "</span>"
if i != lenTags-1 {
tagStr += " "
}
}
return template.HTML(tagStr)
}
revel.TemplateFuncs["msg"] = func(renderArgs map[string]interface{}, message string, args ...interface{}) template.HTML {
str, ok := renderArgs[revel.CurrentLocaleViewArg].(string)
if !ok {
return ""
}
return template.HTML(i18n.Message(str, message, args...))
}
// 不用revel的msg
revel.TemplateFuncs["leaMsg"] = func(renderArgs map[string]interface{}, key string) template.HTML {
locale, _ := renderArgs[revel.CurrentLocaleViewArg].(string)
str := i18n.Message(locale, key)
if strings.HasPrefix(str, "???") {
str = key
}
return template.HTML(str)
}
// lea++
revel.TemplateFuncs["blogTagsLea"] = func(renderArgs map[string]interface{}, tags []string, typeStr string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
}
locale, _ := renderArgs[revel.CurrentLocaleViewArg].(string)
tagStr := ""
lenTags := len(tags)
tagPostUrl := "http://lea.leanote.com/"
if typeStr == "recommend" {
tagPostUrl += "?tag="
} else if typeStr == "latest" {
tagPostUrl += "latest?tag="
} else {
tagPostUrl += "subscription?tag="
}
for i, tag := range tags {
str := revel.Message(locale, tag)
var classes = "label"
if strings.HasPrefix(str, "???") {
str = tag
}
if InArray([]string{"red", "blue", "yellow", "green"}, tag) {
classes += " label-" + tag
} else {
classes += " label-default"
}
classes += " label-post"
var url = tagPostUrl + tag
tagStr += "<a class=\"" + classes + "\" href=\"" + url + "\">" + str + "</a>"
if i != lenTags-1 {
tagStr += " "
}
}
return template.HTML(tagStr)
}
/*
revel.TemplateFuncs["blogTags"] = func(tags []string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
}
// TODO 这里判断语言, 从语言包中拿
tagMap := map[string]string{"red": "红色", "yellow": "黄色", "blue": "蓝色", "green": "绿色"}
tagStr := ""
lenTags := len(tags)
for i, tag := range tags {
if text, ok := tagMap[tag]; ok {
tagStr += text
} else {
tagStr += tag
}
if i != lenTags - 1 {
tagStr += ","
}
}
return template.HTML(tagStr)
}
*/
revel.TemplateFuncs["li"] = func(a string) string {
return ""
}
// str连接
revel.TemplateFuncs["urlConcat"] = func(url string, v ...interface{}) string {
html := ""
for i := 0; i < len(v); i = i + 2 {
item := v[i]
if i+1 == len(v) {
break
}
value := v[i+1]
if item != nil && value != nil {
keyStr, _ := item.(string)
valueStr, err := value.(string)
if !err {
valueInt, _ := value.(int)
valueStr = strconv.Itoa(valueInt)
}
if keyStr != "" && valueStr != "" {
s := keyStr + "=" + valueStr
if html != "" {
html += "&" + s
} else {
html += s
}
}
}
}
if html != "" {
if strings.Index(url, "?") >= 0 {
return url + "&" + html
} else {
return url + "?" + html
}
}
return url
}
revel.TemplateFuncs["urlCond"] = func(url string, sorterI, keyords interface{}) template.HTML {
return ""
}
// http://stackoverflow.com/questions/14226416/go-lang-templates-always-quotes-a-string-and-removes-comments
revel.TemplateFuncs["rawMsg"] = func(renderArgs map[string]interface{}, message string, args ...interface{}) template.JS {
str, ok := renderArgs[revel.CurrentLocaleViewArg].(string)
if !ok {
return ""
}
return template.JS(revel.Message(str, message, args...))
}
// 为后台管理sorter th使用
// 必须要返回HTMLAttr, 返回html, golang 会执行安全检查返回ZgotmplZ
// sorterI 可能是nil, 所以用interfalce{}来接收
/*
data-url="/adminUser/index"
data-sorter="email"
class="th-sortable {{if eq .sorter "email-up"}}th-sort-up{{else}}{{if eq .sorter "email-down"}}th-sort-down{{end}}{{end}}"
*/
revel.TemplateFuncs["sorterTh"] = func(url, sorterField string, sorterI interface{}) template.HTMLAttr {
sorter := ""
if sorterI != nil {
sorter, _ = sorterI.(string)
}
html := "data-url=\"" + url + "\" data-sorter=\"" + sorterField + "\""
html += " class=\"th-sortable "
if sorter == sorterField+"-up" {
html += "th-sort-up\""
} else if sorter == sorterField+"-down" {
html += "th-sort-down"
}
html += "\""
return template.HTMLAttr(html)
}
// pagination
revel.TemplateFuncs["page"] = func(urlBase string, page, pageSize, count int) template.HTML {
if count == 0 {
return ""
}
totalPage := int(math.Ceil(float64(count) / float64(pageSize)))
preClass := ""
prePage := page - 1
if prePage == 0 {
@@ -90,15 +353,10 @@ func init() {
nextClass := ""
nextPage := page + 1
var preUrl, nextUrl string
urlBase := "/blog/" + userId
if notebookId != "" {
urlBase += "/" + notebookId
}
preUrl = urlBase + "?page=" + strconv.Itoa(prePage)
preUrl = urlBase + "?page=" + strconv.Itoa(prePage)
nextUrl = urlBase + "?page=" + strconv.Itoa(nextPage)
// 没有上一页了
if page == 1 {
preClass = "disabled"
@@ -111,9 +369,68 @@ func init() {
}
return template.HTML("<li class='" + preClass + "'><a href='" + preUrl + "'>Previous</a></li> <li class='" + nextClass + "'><a href='" + nextUrl + "'>Next</a></li>")
}
// life
// https://groups.google.com/forum/#!topic/golang-nuts/OEdSDgEC7js
// http://play.golang.org/p/snygrVpQva
// http://grokbase.com/t/gg/golang-nuts/142a6dhfh3/go-nuts-text-template-using-comparison-operators-eq-gt-etc-on-non-existent-variable-causes-the-template-to-stop-outputting-but-with-no-error-correct-behaviour
/*
revel.TemplateFuncs["gt"] = func(a1, a2 interface{}) bool {
switch a1.(type) {
case string:
switch a2.(type) {
case string:
return reflect.ValueOf(a1).String() > reflect.ValueOf(a2).String()
}
case int, int8, int16, int32, int64:
switch a2.(type) {
case int, int8, int16, int32, int64:
return reflect.ValueOf(a1).Int() > reflect.ValueOf(a2).Int()
}
case uint, uint8, uint16, uint32, uint64:
switch a2.(type) {
case uint, uint8, uint16, uint32, uint64:
return reflect.ValueOf(a1).Uint() > reflect.ValueOf(a2).Uint()
}
case float32, float64:
switch a2.(type) {
case float32, float64:
return reflect.ValueOf(a1).Float() > reflect.ValueOf(a2).Float()
}
}
return false
}
*/
/*
{{range $i := N 1 10}}
<div>{{$i}}</div>
{{end}}
*/
revel.TemplateFuncs["N"] = func(start, end int) (stream chan int) {
stream = make(chan int)
go func() {
for i := start; i <= end; i++ {
stream <- i
}
close(stream)
}()
return
}
// init Email
revel.OnAppStart(func() {
// 数据库
db.Init("", "")
// email配置
InitEmail()
InitVd()
// memcache.InitMemcache() // session服务
// 其它service
service.InitService()
controllers.InitService()
admin.InitService()
member.InitService()
service.ConfigS.InitGlobalConfigs()
api.InitService()
})
}
}

View File

@@ -1,15 +1,34 @@
package lea
import (
"encoding/json"
"github.com/revel/revel"
"encoding/json"
"fmt"
"github.com/revel/revel"
)
func Log(i interface{}) {
revel.INFO.Println(i)
func Log(i ...interface{}) {
revel.INFO.Println(i...)
}
func LogW(i ...interface{}) {
revel.WARN.Println(i...)
}
func Log1(key, i interface{}) {
revel.INFO.Println(key, i)
}
func LogJ(i interface{}) {
b, _ := json.MarshalIndent(i, "", " ")
revel.INFO.Println(string(b))
}
b, _ := json.MarshalIndent(i, "", " ")
revel.INFO.Println(string(b))
}
// 为test用
func L(i interface{}) {
fmt.Println(i)
}
func LJ(i interface{}) {
b, _ := json.MarshalIndent(i, "", " ")
fmt.Println(string(b))
}

View File

@@ -1,10 +1,11 @@
package lea
import (
"github.com/revel/revel"
"net/smtp"
"strings"
"github.com/revel/revel"
)
// 发送邮件
var host = "smtp.ym.163.com"
var port = "25"
@@ -12,7 +13,7 @@ var username = "noreply@leanote.com"
var password = "---"
func InitEmail() {
config := revel.Config;
config := revel.Config
host, _ = config.String("email.host")
port, _ = config.String("email.port")
username, _ = config.String("email.username")
@@ -22,7 +23,7 @@ func InitEmail() {
var bodyTpl = `
<html>
<body>
<div style="width: 800px; margin:auto; border-radius:5px; border: 1px solid #ccc; padding: 20px;">
<div style="width: 600px; margin:auto; border-radius:5px; border: 1px solid #ccc; padding: 20px;">
<div>
<div>
<div style="float:left; height: 40px;">
@@ -56,27 +57,27 @@ var bodyTpl = `
</body>
</html>
`
func SendEmail(to, subject, title, body string) bool {
func SendEmailOld(to, subject, body string) bool {
hp := strings.Split(host, ":")
auth := smtp.PlainAuth("", username, password, hp[0])
var content_type string
mailtype := "html"
if mailtype == "html" {
content_type = "Content-Type: text/"+ mailtype + "; charset=UTF-8"
} else{
content_type = "Content-Type: text/" + mailtype + "; charset=UTF-8"
} else {
content_type = "Content-Type: text/plain" + "; charset=UTF-8"
}
// 登录之
body = strings.Replace(bodyTpl, "$body", body, 1)
body = strings.Replace(body, "$title", title, 1)
msg := []byte("To: " + to + "\r\nFrom: " + username + "<"+ username +">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)
//body = strings.Replace(bodyTpl, "$body", body, 1)
//body = strings.Replace(body, "$title", title, 1)
msg := []byte("To: " + to + "\r\nFrom: " + username + "<" + username + ">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)
send_to := strings.Split(to, ";")
err := smtp.SendMail(host+":"+port, auth, username, send_to, msg)
if err != nil {
Log(err)
return false
@@ -84,7 +85,7 @@ func SendEmail(to, subject, title, body string) bool {
return true
}
func SendToLeanote(subject, title, body string) {
func SendToLeanoteOld(subject, title, body string) {
to := "leanote@leanote.com"
SendEmail(to, subject, title, body);
}
SendEmailOld(to, subject, body)
}

View File

@@ -1,9 +1,11 @@
package lea
import (
"strings"
"path/filepath"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// 分离文件名与扩展名(包含.)
@@ -13,14 +15,14 @@ func SplitFilename(filename string) (baseName, ext string) {
ext = SubstringByte(filename, strings.LastIndex(filename, "."))
baseName = strings.TrimRight(filename, ext)
ext = strings.ToLower(ext)
return;
return
}
// 转换文件的格式
// toExt包含.
func TransferExt(path string, toExt string) string {
dir := filepath.Dir(path) + "/" // 文件路径
name := filepath.Base(path) // 文件名 a.jpg
dir := filepath.Dir(path) + "/" // 文件路径
name := filepath.Base(path) // 文件名 a.jpg
// 获取文件名与路径
baseName, _ := SplitFilename(name)
return dir + baseName + toExt
@@ -37,7 +39,7 @@ func GetFilesize(path string) int64 {
if err == nil {
return fileinfo.Size()
}
return 0;
return 0
}
// 清空dir下所有的文件和文件夹
@@ -54,6 +56,14 @@ func ClearDir(dir string) bool {
return true
}
func MkdirAll(dir string) bool {
err := os.MkdirAll(dir, 0777)
if err != nil {
return false
}
return true
}
// list dir's all file, return filenames
func ListDir(dir string) []string {
f, err := os.Open(dir)
@@ -62,4 +72,119 @@ func ListDir(dir string) []string {
}
names, _ := f.Readdirnames(0)
return names
}
}
func CopyFile(srcName, dstName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
func CopyDir(source string, dest string) (err error) {
// get properties of source dir
sourceinfo, err := os.Stat(source)
if err != nil {
return err
}
// create dest dir
err = os.MkdirAll(dest, sourceinfo.Mode())
if err != nil {
return err
}
directory, _ := os.Open(source)
objects, err := directory.Readdir(-1)
for _, obj := range objects {
sourcefilepointer := source + "/" + obj.Name()
destinationfilepointer := dest + "/" + obj.Name()
if obj.IsDir() {
// create sub-directories - recursively
err = CopyDir(sourcefilepointer, destinationfilepointer)
if err != nil {
// fmt.Println(err)
}
} else {
// perform copy
_, err = CopyFile(sourcefilepointer, destinationfilepointer)
if err != nil {
// fmt.Println(err)
}
}
}
return
}
func DeleteFile(path string) bool {
err := os.Remove(path)
if err != nil {
return false
}
return true
}
func IsDirExists(path string) bool {
fi, err := os.Stat(path)
if err != nil {
return os.IsExist(err)
} else {
return fi.IsDir()
}
return false
}
// 获得文件str内容
func GetFileStrContent(path string) string {
fileBytes, err := ioutil.ReadFile(path)
if err != nil {
return ""
}
return string(fileBytes)
}
func IsFileExist(filename string) bool {
var exist = true
if _, err := os.Stat(filename); os.IsNotExist(err) {
exist = false
}
return exist
}
// 写入string内容
func PutFileStrContent(path, content string) bool {
var f *os.File
var err1 error
defer (func() {
if f != nil {
f.Close()
}
})()
f, err1 = os.OpenFile(path, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666) //打开文件
// Log(err1)
// var n int
_, err1 = io.WriteString(f, content) //写入文件(字符串)
// Log(content)
// Log(err1)
// Log(n)
// Log(path)
if err1 != nil {
Log(err1)
return false
}
return true
}

View File

@@ -16,7 +16,7 @@ func TransToGif(path string, maxWidth uint, afterDelete bool) (ok bool, transPat
transPath = path
wand.Genesis()
defer wand.Terminus()
w := wand.NewMagickWand()
defer w.Destroy()
@@ -24,7 +24,7 @@ func TransToGif(path string, maxWidth uint, afterDelete bool) (ok bool, transPat
fmt.Println(err);
return;
}
width := w.ImageWidth()
height := w.ImageHeight()
if maxWidth != 0 {
@@ -34,14 +34,14 @@ func TransToGif(path string, maxWidth uint, afterDelete bool) (ok bool, transPat
width = maxWidth
}
}
w.SetImageFormat("GIF");
if err := paint.Thumbnail(w, width, height); err != nil {
fmt.Println(err);
return;
}
// 判断是否是gif图片, 是就不用转换了
baseName, ext := SplitFilename(path)
var toPath string
@@ -50,19 +50,19 @@ func TransToGif(path string, maxWidth uint, afterDelete bool) (ok bool, transPat
} else {
toPath = TransferExt(path, ".gif");
}
if err := w.WriteImage(toPath); err != nil {
fmt.Println(err);
return;
}
if afterDelete {
os.Remove(path)
}
ok = true
transPath = toPath
return
}
@@ -71,7 +71,7 @@ func TransToGif(path string, maxWidth uint, afterDelete bool) (ok bool, transPat
func Reset(path string, maxWidth uint) (ok bool, transPath string){
wand.Genesis()
defer wand.Terminus()
w := wand.NewMagickWand()
defer w.Destroy()
@@ -79,7 +79,7 @@ func Reset(path string, maxWidth uint) (ok bool, transPath string){
fmt.Println(err);
return;
}
width := w.ImageWidth()
height := w.ImageHeight()
if maxWidth != 0 {
@@ -93,20 +93,20 @@ func Reset(path string, maxWidth uint) (ok bool, transPath string){
fmt.Println(err);
return;
}
toPath := TransferExt(path, ".gif");
if err := w.WriteImage(toPath); err != nil {
fmt.Println(err);
return;
}
ok = true
transPath = toPath
return
}
*/
func TransToGif(path string, maxWidth uint, afterDelete bool) (ok bool, transPath string) {
return ok, path
}
}

22
app/lea/Pwd.go Normal file
View File

@@ -0,0 +1,22 @@
package lea
// 对比密码是否一致
// 因为之前密码是用md5加密的, 所以通过密码长度来判断
// rawPwd 原始, 用户输入的密码
func ComparePwd(rawPwd, dbPwd string) bool {
if len(dbPwd) == 32 {
return Md5(rawPwd) == dbPwd
}
hex := []byte(dbPwd)
return CompareHash(hex, rawPwd)
}
// 加密
func GenPwd(rawPwd string) string {
digest, err := GenerateHash(rawPwd)
if err != nil {
return ""
}
return string(digest)
}

View File

@@ -1,16 +1,19 @@
package lea
import (
"fmt"
"regexp"
"bytes"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"io"
"fmt"
"github.com/PuerkitoBio/goquery"
"gopkg.in/mgo.v2/bson"
"io"
math_rand "math/rand"
"regexp"
"strings"
"time"
math_rand "math/rand"
)
// 字符串
@@ -22,6 +25,22 @@ func Md5(s string) string {
return hex.EncodeToString(h.Sum(nil))
}
// 3位数的转换, 为了用bson.id -> 3位数
func Digest3(str string) string {
var b rune = 0
for _, k := range str {
b += k
}
return fmt.Sprintf("%d", b%1000)
}
func Digest2(str string) string {
var b rune = 0
for _, k := range str {
b += k
}
return fmt.Sprintf("%d", b%100)
}
// Guid
func NewGuid() string {
b := make([]byte, 48)
@@ -58,7 +77,7 @@ func Substr(str string, start, length int) string {
func substr(str string, start, length int, isRune bool) string {
rs := []rune(str)
rs2 := []byte(str)
rl := len(rs)
if !isRune {
rl = len(rs2)
@@ -122,39 +141,49 @@ func ReplaceAll(oldStr, pattern, newStr string) string {
return string(s)
}
func SubStringHTML(param string, length int, end string) string {
// 先取出<pre></pre>占位..
result := ""
// 1
// 获取纯文本
func SubStringHTMLToRaw(param string, length int) string {
if param == "" {
return param
}
n := 0
var temp rune // 中文问题, 用rune来解决
isCode := false //是不是HTML代码
isHTML := false //是不是HTML特殊字符,如&nbsp;
rStr := []rune(param)
for i := 0; i < len(rStr); i++ {
lenStr := len(rStr)
isCode := false
resultRune := make([]rune, length)
// s := ""
for i := 0; i < lenStr; i++ {
temp = rStr[i]
if temp == '<' {
isCode = true
} else if temp == '&' {
isHTML = true
} else if temp == '>' && isCode {
n = n - 1
continue
} else if temp == '>' {
isCode = false
} else if temp == ';' && isHTML {
isHTML = false
resultRune[n] = ' '
n++
if n >= length {
break
}
continue
}
if !isCode && !isHTML {
n = n + 1
}
result += string(temp)
if n >= length {
break
if !isCode {
resultRune[n] = temp
// s += string(temp)
n++
if n >= length {
break
}
}
}
result += end
result := string(resultRune[0:n])
return strings.Trim(result, " ")
}
// 自带方法补全html
func fixHtml(result string) string {
// 取出所有标签
tempResult := ReplaceAll(result, "(>)[^<>]*(<?)", "$1$2") // 把标签中间的所有内容都去掉了
@@ -163,13 +192,13 @@ func SubStringHTML(param string, length int, end string) string {
// 把<div class=xxx的class=xxx去掉
tempResult = ReplaceAll(tempResult, "<(/?[a-zA-Z]+)[^<>]*>", "<$1>")
// 3 只能用正则,+stack来去有结束的
// golang的正则暂不支持back reference, 以后可以用它来去掉重复的标签
p, _ := regexp.Compile("<(/?[a-zA-Z]+)[^<>]*>") // 得到所有的<div>, </div>...
strs := p.FindAllString(tempResult, -1)
// fmt.Println(strs)
// fmt.Println(strs)
stack := make([]string, len(strs))
stackP := -1
for _, each := range strs {
@@ -186,16 +215,82 @@ func SubStringHTML(param string, length int, end string) string {
// 补全tag
if stackP != -1 {
fmt.Println(stack[0 : stackP+1])
for _, each := range stack[0 : stackP+1] {
if each[1] != '/' {
result += "</" + each[1:]
}
}
}
return result
}
// 获取摘要, HTML
func SubStringHTML(param string, length int, end string) string {
if param == "" {
return param
}
result := ""
rStr := []rune(param)
lenStr := len(rStr)
if lenStr <= length {
result = param
} else {
// 1
n := 0
var temp rune // 中文问题, 用rune来解决
isCode := false //是不是HTML代码
isHTML := false //是不是HTML特殊字符,如&nbsp;
var i = 0
for ; i < lenStr; i++ {
temp = rStr[i]
if temp == '<' {
isCode = true
} else if temp == '&' {
isHTML = true
} else if temp == '>' && isCode {
// n = n - 1
isCode = false
} else if temp == ';' && isHTML {
isHTML = false
}
if !isCode && !isHTML {
n = n + 1
}
// 每一次都相加, 速度非常慢!, 重新分配内存, 7倍的差距
// result += string(temp)
if n >= length {
break
}
}
result = string(rStr[0:i])
if end != "" {
result += end
}
}
// 使用goquery来取出html, 为了补全html
htmlReader := bytes.NewBufferString(result)
dom, err1 := goquery.NewDocumentFromReader(htmlReader)
if err1 == nil {
html, _ := dom.Html()
html = strings.Replace(html, "<html><head></head><body>", "", 1)
html = strings.Replace(html, "</body></html>", "", 1)
// TODO 把style="float: left"去掉
return html
// 如果有错误, 则使用自己的方法补全, 有风险
} else {
return fixHtml(result)
}
}
// 是否是合格的密码
func IsGoodPwd(pwd string) (bool, string) {
if pwd == "" {
@@ -210,16 +305,16 @@ func IsGoodPwd(pwd string) (bool, string) {
// 是否是email
func IsEmail(email string) bool {
if email == "" {
return false;
return false
}
ok, _ := regexp.MatchString(`^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[0-9a-zA-Z]{2,3}$`, email)
ok, _ := regexp.MatchString(`^([a-zA-Z0-9]+[_|\_|\.|\-]?)*[_a-z\-A-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.|\-]?)*[a-zA-Z0-9\-]+\.[0-9a-zA-Z]{2,6}$`, email)
return ok
}
// 是否只包含数字, 字母 -, _
func IsUsername(username string) bool {
if username == "" {
return false;
return false
}
ok, _ := regexp.MatchString(`[^0-9a-zA-Z_\-]`, username)
return !ok
@@ -256,14 +351,83 @@ func RandomPwd(num int) string {
chars[j] = byte(i)
j++
}
j--;
j--
str := ""
math_rand.Seed(time.Now().UnixNano())
for i := 0; i < num; i++ {
x := math_rand.Intn(j)
str += string(chars[x])
}
return str
}
}
func InArray(arr []string, str string) bool {
if arr == nil {
return false
}
for _, v := range arr {
if v == str {
return true
}
}
return false
}
// 将名称的特殊字符去掉
func FixFilename(filename string) string {
if filename != "" {
// 把特殊字段给替换掉
// str := `life "%&()+,/:;<>=?@\|`
// . == \\.
// $ === \\$
reg, _ := regexp.Compile("\\.|/|#|\\$|!|\\^|\\*|'| |\"|%|&|\\(|\\)|\\+|\\,|/|:|;|<|>|=|\\?|@|\\||\\\\")
filename = reg.ReplaceAllString(filename, "-")
filename = strings.Trim(filename, "-") // 左右单独的-去掉
// 把空格替换成-
// filename = strings.Replace(filename, " ", "-", -1)
for strings.Index(filename, "--") >= 0 { // 防止出现连续的--
filename = strings.Replace(filename, "--", "-", -1)
}
return filename
}
return filename
}
// 是否是合法的时间
// 不是, 0001-01-01T00:00:00Z, 且比今天小
func IsValidTime(t time.Time) bool {
if t.Year() > 20 {
now := time.Now()
Log("------")
Log(t)
Log(now)
if t.Before(now) {
return true
}
}
return false
}
// url传过来的时间没有时区信息, 转到本地时间
func ToLocalTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.Local)
}
// 修复传过来的时间, 如果比今天大, 则设为现在
func FixUrlTime(t time.Time) time.Time {
localTime := ToLocalTime(t)
if IsValidTime(localTime) {
return localTime
}
return time.Now()
}
// 得到用户的随机文件路径 3位/userId/2位
func GetRandomFilePath(userId, uuid string) string {
if uuid == "" {
uuid = NewGuid()
}
return Digest3(userId) + "/" + userId + "/" + Digest2(uuid)
}

170
app/lea/Vd.go Normal file
View File

@@ -0,0 +1,170 @@
package lea
import (
"encoding/json"
"regexp"
"strconv"
)
// 验证
var rulesStr = `{
"username": [
{"rule": "required", "msg": "inputUsername"},
{"rule": "noSpecialChars", "msg": "noSpecialChars"},
{"rule": "minLength", "data": "4", "msg": "minLength", "msgData": "4"}
],
"email": [
{"rule": "required", "msg": "inputEmail"},
{"rule": "email", "msg": "errorEmail"}
],
"password": [
{"rule": "required", "msg": "inputPassword"},
{"rule": "password", "msg": "errorPassword"}
],
"subDomain": [
{"rule": "subDomain", "msg": "errorSubDomain"}
],
"domain": [
{"rule": "domain", "msg": "errorDomain"}
],
"perPageSize": [
{"rule": "min", "data": "1", "msg": "errorPerPageSize"}
],
"sortField": [
{"rule": "sortField", "msg": "errorSortField"}
]
}
`
var rulesMap map[string][]map[string]string
var rules = map[string]func(string, map[string]string) (bool, string){
"required": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
return
}
ok = true
return
},
"minLength": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
return
}
data := rule["data"]
dataI, _ := strconv.Atoi(data)
ok = len(value) >= dataI
return
},
"min": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
return
}
data := rule["data"]
dataI, _ := strconv.Atoi(data)
vI, _ := strconv.Atoi(value)
ok = vI >= dataI
return
},
"sortField": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
return
}
sortFields := []string{"PublicTime", "CreatedTime", "UpdatedTime", "Title"}
ok = InArray(sortFields, value)
return
},
"password": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
return
}
ok = len(value) >= 6
return
},
"email": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
return
}
ok = IsEmail(value)
return
},
"noSpecialChars": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
return
}
ok = IsUsername(value)
return
},
// www.baidu.com
//
"domain": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
ok = true
return // 可为空
}
ok2, _ := regexp.MatchString(`[^0-9a-zA-Z_\.\-]`, value)
ok = !ok2
if !ok {
return
}
ok = true
return
},
// abcd
"subDomain": func(value string, rule map[string]string) (ok bool, msg string) {
if value == "" {
ok = true
return // 可为空
}
if len(value) < 4 {
ok = false
return
}
ok2, _ := regexp.MatchString(`[^0-9a-zA-Z_\-]`, value)
ok = !ok2
return
},
}
func InitVd() {
json.Unmarshal([]byte(rulesStr), &rulesMap)
LogJ(rulesMap)
}
// 验证
// Vd("username", "life")
func Vd(name, value string) (ok bool, msg string) {
rs, _ := rulesMap[name]
for _, rule := range rs {
ruleFunc, _ := rules[rule["rule"]]
if ok2, msg2 := ruleFunc(value, rule); !ok2 {
ok = false
if msg2 != "" {
msg = msg2
} else {
msg = rule["msg"]
}
msgData := rule["msgData"]
if msgData != "" {
msg += "-" + msgData
}
return
}
}
ok = true
return
}
func Vds(m map[string]string) (ok bool, msg string) {
for name, value := range m {
ok, msg = Vd(name, value)
if !ok {
return
}
}
ok = true
return
}

230
app/lea/archive/tar.go Normal file
View File

@@ -0,0 +1,230 @@
package archive
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path"
)
// main functions shows how to TarGz a directory/file and
// UnTarGz a file
// Gzip and tar from source directory or file to destination file
// you need check file exist before you call this function
func main() {
/*
os.Mkdir("/home/ty4z2008/tar", 0777)
w, err := CopyFile("/home/ty4z2008/tar/1.pdf", "/home/ty4z2008/src/1.pdf")
//targetfile,sourcefile
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(w)
TarGz("/home/ty4z2008/tar/1.pdf", "/home/ty4z2008/test.tar.gz") //压缩
//UnTarGz("/home/ty4z2008/1.tar.gz", "/home/ty4z2008") //解压
os.RemoveAll("/home/ty4z2008/tar")
*/
// TaZip("/Users/life/Desktop/j", "/Users/life/Desktop/aaa.tar.gz")
Zip("/Users/life/Desktop/j", "/Users/life/Desktop/aaa.zip")
fmt.Println("ok")
}
func TarGz(srcDirPath string, destFilePath string) (ok bool) {
defer func() { //必须要先声明defer否则不能捕获到panic异常
if err := recover(); err != nil {
ok = false
}
}()
fw, err := os.Create(destFilePath)
if err != nil {
panic(err)
}
defer fw.Close()
// Gzip writer
gw := gzip.NewWriter(fw)
defer gw.Close()
// Tar writer
tw := tar.NewWriter(gw)
defer tw.Close()
// Check if it's a file or a directory
f, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
fi, err := f.Stat()
if err != nil {
panic(err)
}
if fi.IsDir() {
// handle source directory
// fmt.Println("Cerating tar.gz from directory...")
tarGzDir(srcDirPath, path.Base(srcDirPath), tw)
} else {
// handle file directly
// fmt.Println("Cerating tar.gz from " + fi.Name() + "...")
tarGzFile(srcDirPath, fi.Name(), tw, fi)
}
ok = true
return
}
// Deal with directories
// if find files, handle them with tarGzFile
// Every recurrence append the base path to the recPath
// recPath is the path inside of tar.gz
func tarGzDir(srcDirPath string, recPath string, tw *tar.Writer) {
// Open source diretory
dir, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
defer dir.Close()
// Get file info slice
fis, err := dir.Readdir(0)
if err != nil {
panic(err)
}
for _, fi := range fis {
// Append path
curPath := srcDirPath + "/" + fi.Name()
// Check it is directory or file
if fi.IsDir() {
// Directory
// (Directory won't add unitl all subfiles are added)
// fmt.Printf("Adding path...%s\n", curPath)
tarGzDir(curPath, recPath+"/"+fi.Name(), tw)
} else {
// File
// fmt.Printf("Adding file...%s\n", curPath)
}
tarGzFile(curPath, recPath+"/"+fi.Name(), tw, fi)
}
}
// Deal with files
func tarGzFile(srcFile string, recPath string, tw *tar.Writer, fi os.FileInfo) {
if fi.IsDir() {
// fmt.Println("??")
// Create tar header
hdr := new(tar.Header)
// if last character of header name is '/' it also can be directory
// but if you don't set Typeflag, error will occur when you untargz
hdr.Name = recPath // + "/"
// fmt.Println(hdr.Name)
hdr.Typeflag = tar.TypeDir
// hdr.Size = 0
//hdr.Mode = 0755 | c_ISDIR
// hdr.Mode = int64(fi.Mode()) // 加这个会有错误!!!
// hdr.ModTime = fi.ModTime() // 加这个会有错误!!
// Write hander
err := tw.WriteHeader(hdr)
if err != nil {
panic(err)
}
} else {
// File reader
fr, err := os.Open(srcFile)
if err != nil {
panic(err)
}
defer fr.Close()
// Create tar header
hdr := new(tar.Header)
hdr.Name = recPath
// fmt.Println(hdr.Name)
hdr.Size = fi.Size()
hdr.Mode = int64(fi.Mode())
hdr.ModTime = fi.ModTime()
// Write hander
err = tw.WriteHeader(hdr)
if err != nil {
panic(err)
}
// Write file data
_, err = io.Copy(tw, fr)
if err != nil {
panic(err)
}
}
}
// Ungzip and untar from source file to destination directory
// you need check file exist before you call this function
func UnTarGz(srcFilePath string, destDirPath string) {
// fmt.Println("UnTarGzing " + srcFilePath + "...")
// Create destination directory
os.Mkdir(destDirPath, os.ModePerm)
fr, err := os.Open(srcFilePath)
if err != nil {
panic(err)
}
defer fr.Close()
// Gzip reader
gr, err := gzip.NewReader(fr)
if err != nil {
panic(err)
}
defer gr.Close()
// Tar reader
tr := tar.NewReader(gr)
for {
hdr, err := tr.Next()
if err == io.EOF {
// End of tar archive
break
}
//handleError(err)
// fmt.Println("UnTarGzing file..." + hdr.Name)
// Check if it is diretory or file
if hdr.Typeflag != tar.TypeDir {
// Get files from archive
// Create diretory before create file
os.MkdirAll(destDirPath+"/"+path.Dir(hdr.Name), os.ModePerm)
// Write data to file
fw, _ := os.Create(destDirPath + "/" + hdr.Name)
if err != nil {
panic(err)
}
_, err = io.Copy(fw, tr)
if err != nil {
panic(err)
}
}
}
// fmt.Println("Well done!")
}
// Copyfile
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}

199
app/lea/archive/zip.go Normal file
View File

@@ -0,0 +1,199 @@
package archive
import (
"archive/zip"
"fmt"
"io"
"os"
"path"
"strings"
"github.com/leanote/leanote/app/lea"
)
// main functions shows how to TarGz a directory/file and
// UnTarGz a file
// Gzip and tar from source directory or file to destination file
// you need check file exist before you call this function
func Zip(srcDirPath string, destFilePath string) (ok bool) {
defer func() { //必须要先声明defer否则不能捕获到panic异常
if err := recover(); err != nil {
ok = false
}
}()
fw, err := os.Create(destFilePath)
if err != nil {
panic(err)
}
defer fw.Close()
// Tar writer
tw := zip.NewWriter(fw)
defer tw.Close()
// Check if it's a file or a directory
f, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
fi, err := f.Stat()
if err != nil {
panic(err)
}
if fi.IsDir() {
// handle source directory
// fmt.Println("Cerating tar.gz from directory...")
zipDir(srcDirPath, path.Base(srcDirPath), tw)
} else {
// handle file directly
// fmt.Println("Cerating tar.gz from " + fi.Name() + "...")
zipFile(srcDirPath, fi.Name(), tw, fi)
}
ok = true
return
}
// Deal with directories
// if find files, handle them with zipFile
// Every recurrence append the base path to the recPath
// recPath is the path inside of tar.gz
func zipDir(srcDirPath string, recPath string, tw *zip.Writer) {
// Open source diretory
dir, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
defer dir.Close()
// Get file info slice
fis, err := dir.Readdir(0)
if err != nil {
panic(err)
}
for _, fi := range fis {
// Append path
curPath := srcDirPath + "/" + fi.Name()
// Check it is directory or file
if fi.IsDir() {
// Directory
// (Directory won't add unitl all subfiles are added)
// fmt.Printf("Adding path...%s\n", curPath)
zipDir(curPath, recPath+"/"+fi.Name(), tw)
} else {
// File
// fmt.Printf("Adding file...%s\n", curPath)
}
zipFile(curPath, recPath+"/"+fi.Name(), tw, fi)
}
}
// Deal with files
func zipFile(srcFile string, recPath string, tw *zip.Writer, fi os.FileInfo) {
if fi.IsDir() {
// fmt.Println("??")
// Create tar header
/*
fh, err := zip.FileInfoHeader(fi)
if err != nil {
panic(err)
}
fh.Name = recPath // + "/"
err = tw.WriteHeader(hdr)
tw.Create(recPath)
*/
} else {
// File reader
fr, err := os.Open(srcFile)
if err != nil {
panic(err)
}
defer fr.Close()
// Write hander
w, err2 := tw.Create(recPath)
if err2 != nil {
panic(err)
}
// Write file data
_, err = io.Copy(w, fr)
if err != nil {
panic(err)
}
}
}
// Ungzip and untar from source file to destination directory
// you need check file exist before you call this function
func Unzip(srcFilePath string, destDirPath string) (ok bool, msg string) {
ok = false
msg = ""
defer func() { //必须要先声明defer否则不能捕获到panic异常
if err := recover(); err != nil {
msg = fmt.Sprintf("%v", err)
ok = false
}
}()
os.Mkdir(destDirPath, os.ModePerm)
r, err := zip.OpenReader(srcFilePath)
if err != nil {
panic(err)
}
defer r.Close()
for _, f := range r.File {
// fmt.Println("FileName : ", f.Name); // j/aaa.zip
rc, err := f.Open()
if err != nil {
panic(err)
}
// 包含恶意目录
if strings.Contains(f.Name, "../") {
lea.LogW("恶意文件", f.Name);
continue
}
// 把首文件夹去掉, 即j去掉, 分离出文件夹和文件名
paths := strings.Split(f.Name, "/")
prePath := ""
filename := ""
l := len(paths)
// fmt.Println(l)
if l > 1 {
// 去掉第1个文件夹
if l == 2 {
filename = paths[1]
} else {
filename = paths[l-1]
prePath = strings.Join(paths[1:l-1], "/")
}
} else {
filename = f.Name
}
// fmt.Println(prePath)
// 相对于目标文件件下的路径
destPath := destDirPath + "/" + filename
if prePath != "" {
os.MkdirAll(destDirPath+"/"+prePath, os.ModePerm)
destPath = destDirPath + "/" + prePath + "/" + filename
}
// Write data to file
// fmt.Println(destPath)
fw, _ := os.Create(destPath)
if err != nil {
panic(err)
}
_, err = io.Copy(fw, rc)
if err != nil {
panic(err)
}
}
ok = true
return
}

View File

@@ -1,9 +1,9 @@
package binder
import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/controllers"
"github.com/revel/revel"
// "github.com/leanote/leanote/app/controllers/api"
"fmt"
"reflect"
"strings"
@@ -11,7 +11,7 @@ import (
// leanote binder struct
// rewrite revel struct binder
// not need the struct name as prefix,
// not need the struct name as prefix,
// eg:
// type Note struct {Name}
// func (c Controller) List(note Note) revel.Result {}
@@ -33,13 +33,13 @@ var MSSBinder = revel.Binder{
}
return result
},
Unbind: func(output map[string]string, name string, val interface{}) {
mapValue := reflect.ValueOf(val)
for _, key := range mapValue.MapKeys() {
revel.Unbind(output, fmt.Sprintf("%v", key.Interface()),
mapValue.MapIndex(key).Interface())
}
}
},
}
@@ -53,31 +53,56 @@ func nextKey(key string) string {
}
return key[:fieldLen]
}
var leanoteStructBinder = revel.Binder{
// name == "noteOrContent"
Bind: func(params *revel.Params, name string, typ reflect.Type) reflect.Value {
result := reflect.New(typ).Elem()
result := reflect.New(typ).Elem() // 创建一个该类型的, 然后其field从所有的param去取
fieldValues := make(map[string]reflect.Value)
for key, _ := range params.Values {
// fmt.Println(name)
// fmt.Println(typ) // api.NoteFiles
// name = files[0], files[1], noteContent
// fmt.Println(params.Values)
/*
map[Title:[test1] METHOD:[POST] NotebookId:[54c4f51705fcd14031000002]
files[1][FileId]:[]
controller:[note]
files[1][LocalFileId]:[54c7ae27d98d0329dd000000] files[1][HasBody]:[true] files[0][FileId]:[] files[0][LocalFileId]:[54c7ae855e94ea2dba000000] action:[addNote] Content:[<p>lifedddddd</p><p><img src="app://leanote/data/54bdc65599c37b0da9000002/images/1422368307147_2.png" alt="" data-mce-src="app://leanote/data/54bdc65599c37b0da9000002/images/1422368307147_2.png" style="display: block; margin-left: auto; margin-right: auto;"></p><p><img src="http://127.0.0.1:8008/api/file/getImage?fileId=54c7ae27d98d0329dd000000" alt="" data-mce-src="http://127.0.0.1:8008/api/file/getImg?fileId=54c7ae27d98d0329dd000000"></p><p><br></p><p><img src="http://127.0.0.1:8008/api/file/getImage?fileId=54c7ae855e94ea2dba000000" alt="" data-mce-src="http://127.0.0.1:8008/api/file/getImage?fileId=54c7ae855e94ea2dba000000" style="display: block; margin-left: auto; margin-right: auto;"></p><p><br></p><p><br></p>] IsBlog:[false] token:[user1]
files[0][HasBody]:[true]]
*/
nameIsSlice := strings.Contains(name, "[")
// fmt.Println(params.Values["files[1]"])
// fmt.Println(params.Values["Title"])
for key, _ := range params.Values { // Title, Content, Files
// 这里, 如果没有点, 默认就是a.
// life
// fmt.Println("key:" + key); // files[0][LocalFileId]
// fmt.Println("name:" + name); // files[0][LocalFileId]
var suffix string
var noPrefix = false
if !strings.HasPrefix(key, name + ".") {
if nameIsSlice && strings.HasPrefix(key, name) {
suffix = key[len(name)+1 : len(key)-1] // files[0][LocalFileId] 去掉 => LocalFileId
} else if !strings.HasPrefix(key, name+".") {
noPrefix = true
suffix = key
// continue
// continue
} else {
// Get the name of the struct property.
// Strip off the prefix. e.g. foo.bar.baz => bar.baz
suffix = key[len(name)+1:]
}
// fmt.Println(suffix);
fieldName := nextKey(suffix) // e.g. bar => "bar", bar.baz => "bar", bar[0] => "bar"
// fmt.Println(fieldName);
fieldLen := len(fieldName)
if _, ok := fieldValues[fieldName]; !ok {
// Time to bind this field. Get it and make sure we can set it.
fieldName = strings.Title(fieldName) // 传过来title, 但struct是Title
// fmt.Println("xx: " + fieldName)
fieldValue := result.FieldByName(fieldName)
// fmt.Println(fieldValue)
if !fieldValue.IsValid() {
continue
}
@@ -86,9 +111,17 @@ var leanoteStructBinder = revel.Binder{
}
var boundVal reflect.Value
// 没有name前缀
if(noPrefix) {
if noPrefix {
// life
// fmt.Println("<<")
// fmt.Println(strings.Title(key[:fieldLen]));
boundVal = revel.Bind(params, key[:fieldLen], fieldValue.Type())
} else {
// fmt.Println("final")
// fmt.Println(key[:len(name)+1+fieldLen]) // files[0][HasBody
if nameIsSlice {
fieldLen += 1
}
boundVal = revel.Bind(params, key[:len(name)+1+fieldLen], fieldValue.Type())
}
fieldValue.Set(boundVal)
@@ -116,5 +149,8 @@ func init() {
revel.TypeBinders[reflect.TypeOf(info.UserBlogComment{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(info.UserBlogStyle{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(info.Notebook{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(controllers.NoteOrContent{})] = leanoteStructBinder
}
revel.TypeBinders[reflect.TypeOf(info.UserAccount{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(info.NoteOrContent{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(info.ApiNote{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(info.NoteFile{})] = leanoteStructBinder
}

298
app/lea/blog/Template.go Normal file
View File

@@ -0,0 +1,298 @@
package blog
import (
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"html/template"
"io/ioutil"
// "os"
"bytes"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
)
//--------------------
// leanote 自定义主题
// 不使用revel的模板机制
// By life
//--------------------
var ts = []string{"header.html", "footer.html", "highlight.html", "comment.html", "view.html", "404.html"}
var selfTs = []string{"header.html", "footer.html", "index.html", "about_me.html"} // 用户自定义的文件列表
type BlogTpl struct {
Template *template.Template
PathContent map[string]string // path => content
}
func (this *BlogTpl) Content(name string) string {
return this.PathContent[name]
}
var BlogTplObject *BlogTpl
var CloneTemplate *template.Template
type RenderTemplateResult struct {
Template *template.Template
PathContent map[string]string
ViewArgs map[string]interface{}
IsPreview bool // 是否是预览
CurBlogTpl *BlogTpl
}
func parseTemplateError(err error) (templateName string, line int, description string) {
description = err.Error()
i := regexp.MustCompile(`:\d+:`).FindStringIndex(description)
if i != nil {
line, err = strconv.Atoi(description[i[0]+1 : i[1]-1])
if err != nil {
}
templateName = description[:i[0]]
if colon := strings.Index(templateName, ":"); colon != -1 {
templateName = templateName[colon+1:]
}
templateName = strings.TrimSpace(templateName)
description = description[i[1]+1:]
}
return templateName, line, description
}
func (r *RenderTemplateResult) render(req *revel.Request, resp *revel.Response, wr io.Writer) {
err := r.Template.Execute(wr, r.ViewArgs)
if err == nil {
return
}
var templateContent []string
templateName, line, description := parseTemplateError(err)
var content = ""
if templateName == "" {
templateName = r.Template.Name()
content = r.PathContent[templateName]
} else {
content = r.PathContent[templateName]
}
if content != "" {
templateContent = strings.Split(content, "\n")
}
compileError := &revel.Error{
Title: "Template Execution Error",
Path: templateName,
Description: description,
Line: line,
SourceLines: templateContent,
}
// 这里, 错误!!
// 这里应该导向到本主题的错误页面
resp.Status = 500
ErrorResult{r.ViewArgs, compileError, r.IsPreview, r.CurBlogTpl}.Apply(req, resp)
}
func (r *RenderTemplateResult) Apply(req *revel.Request, resp *revel.Response) {
// Handle panics when rendering templates.
defer func() {
if err := recover(); err != nil {
}
}()
chunked := revel.Config.BoolDefault("results.chunked", false)
// If it's a HEAD request, throw away the bytes.
out := io.Writer(resp.Out)
if req.Method == "HEAD" {
out = ioutil.Discard
}
// In a prod mode, write the status, render, and hope for the best.
// (In a dev mode, always render to a temporary buffer first to avoid having
// error pages distorted by HTML already written)
if chunked && !revel.DevMode {
resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
r.render(req, resp, out) // 这里!!!
return
}
// Render the template into a temporary buffer, to see if there was an error
// rendering the template. If not, then copy it into the response buffer.
// Otherwise, template render errors may result in unpredictable HTML (and
// would carry a 200 status code)
var b bytes.Buffer
r.render(req, resp, &b)
if !chunked {
resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len()))
}
resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
b.WriteTo(out)
}
// 博客模板
func Init() {
BlogTplObject = &BlogTpl{PathContent: map[string]string{}}
BlogTplObject.Template = template.New("blog").Funcs(revel.TemplateFuncs)
for _, path := range ts {
fileBytes, _ := ioutil.ReadFile(revel.ViewsPath + "/Blog/" + path)
fileStr := string(fileBytes)
path := "blog/" + path
// path := path
BlogTplObject.PathContent[path] = fileStr
BlogTplObject.Template.New(path).Parse(fileStr) // 以blog为根
}
// 复制一份
CloneTemplate, _ = BlogTplObject.Template.Clone()
}
// name = index.html, search.html, cate.html, page.html
// basePath 表未用户主题的基路径, 如/xxx/public/upload/32323232/themes/theme1, 如果没有, 则表示用自带的
// isPreview 如果是, 错误提示则显示系统的 500 错误详情信息, 供debug
//
func RenderTemplate(name string, args map[string]interface{}, basePath string, isPreview bool) revel.Result {
var r *RenderTemplateResult
// 传来的主题路径为空, 则用系统的
// 都不会为空的
if basePath == "" {
path := "blog/" + name
// path := name
t := BlogTplObject.Template.Lookup(path)
r = &RenderTemplateResult{
Template: t,
PathContent: BlogTplObject.PathContent, // 为了显示错误
ViewArgs: args, // 把args给它
}
} else {
// 复制一份
newBlogTplObject := &BlogTpl{}
var err error
newBlogTplObject.Template, err = CloneTemplate.Clone() // 复制一份, 为防止多用户出现问题, 因为newBlogTplObject是全局的
if err != nil {
return nil
}
newBlogTplObject.PathContent = map[string]string{}
for k, v := range BlogTplObject.PathContent {
newBlogTplObject.PathContent[k] = v
}
// 将该basePath下的所有文件提出
files := ListDir(basePath)
for _, t := range files {
if !strings.Contains(t, ".html") {
continue
}
fileBytes, err := ioutil.ReadFile(basePath + "/" + t)
if err != nil {
continue
}
fileStr := string(fileBytes)
newBlogTplObject.PathContent[t] = fileStr
newBlogTplObject.Template.New(t).Parse(fileStr)
}
// 如果本主题下没有, 则用系统的
t := newBlogTplObject.Template.Lookup(name)
if t == nil {
path := "blog/" + name
t = BlogTplObject.Template.Lookup(path)
}
r = &RenderTemplateResult{
Template: t,
PathContent: newBlogTplObject.PathContent, // 为了显示错误
ViewArgs: args,
CurBlogTpl: newBlogTplObject,
IsPreview: isPreview,
}
}
return r
}
////////////////////
//
type ErrorResult struct {
ViewArgs map[string]interface{}
Error error
IsPreview bool
CurBlogTpl *BlogTpl
}
// 错误显示出
func (r ErrorResult) Apply(req *revel.Request, resp *revel.Response) {
format := req.Format
status := resp.Status
if status == 0 {
status = http.StatusInternalServerError
}
contentType := revel.ContentTypeByFilename("xxx." + format)
if contentType == revel.DefaultFileContentType {
contentType = "text/plain"
}
// Get the error template.
var err error
templatePath := fmt.Sprintf("errors/%d.%s", status, format)
err = nil
// tmpl, err := revel.MainTemplateLoader.Template("index.html") // 这里找到错误页面主题
// This func shows a plaintext error message, in case the template rendering
// doesn't work.
showPlaintext := func(err error) {
revel.PlaintextErrorResult{fmt.Errorf("Server Error:\n%s\n\n"+
"Additionally, an error occurred when rendering the error page:\n%s",
r.Error, err)}.Apply(req, resp)
}
// 根据是否是preview来得到404模板
// 是, 则显示系统的错误信息, blog-500.html
var tmpl *template.Template
if r.IsPreview {
tmpl = r.CurBlogTpl.Template.Lookup("blog/404.html")
} else {
tmpl = r.CurBlogTpl.Template.Lookup("404.html")
}
if tmpl == nil {
if err == nil {
err = fmt.Errorf("Couldn't find template %s", templatePath)
}
showPlaintext(err)
return
}
// If it's not a revel error, wrap it in one.
var revelError *revel.Error
switch e := r.Error.(type) {
case *revel.Error:
revelError = e
case error:
revelError = &revel.Error{
Title: "Server Error",
Description: e.Error(),
}
}
if revelError == nil {
panic("no error provided")
}
if r.ViewArgs == nil {
r.ViewArgs = make(map[string]interface{})
}
r.ViewArgs["Error"] = revelError
r.ViewArgs["Router"] = revel.MainRouter
// 不是preview就不要显示错误了
if r.IsPreview {
var b bytes.Buffer
out := io.Writer(resp.Out)
// out = ioutil.Discard
err = tmpl.Execute(&b, r.ViewArgs)
resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
b.WriteTo(out)
}
}

401
app/lea/captcha/Captcha.go Normal file
View File

@@ -0,0 +1,401 @@
package captcha
import (
crand "crypto/rand"
"image"
"image/color"
"image/png"
"io"
"math/rand"
"strconv"
"time"
)
const (
stdWidth = 100
stdHeight = 40
maxSkew = 2
)
const (
fontWidth = 5
fontHeight = 8
blackChar = 1
)
var font = [][]byte{
{ // 0
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
},
{ // 1
0, 0, 1, 0, 0,
0, 1, 1, 0, 0,
1, 0, 1, 0, 0,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
1, 1, 1, 1, 1,
},
{ // 2
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
0, 0, 0, 0, 1,
0, 0, 0, 1, 1,
0, 1, 1, 0, 0,
1, 0, 0, 0, 0,
1, 0, 0, 0, 0,
1, 1, 1, 1, 1,
},
{ // 3
1, 1, 1, 1, 0,
0, 0, 0, 0, 1,
0, 0, 0, 1, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
1, 1, 1, 1, 0,
},
{ // 4
1, 0, 0, 1, 0,
1, 0, 0, 1, 0,
1, 0, 0, 1, 0,
1, 0, 0, 1, 0,
1, 1, 1, 1, 1,
0, 0, 0, 1, 0,
0, 0, 0, 1, 0,
0, 0, 0, 1, 0,
},
{ // 5
1, 1, 1, 1, 1,
1, 0, 0, 0, 0,
1, 0, 0, 0, 0,
1, 1, 1, 1, 0,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
1, 1, 1, 1, 0,
},
{ // 6
0, 0, 1, 1, 1,
0, 1, 0, 0, 0,
1, 0, 0, 0, 0,
1, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
},
{ // 7
1, 1, 1, 1, 1,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
0, 0, 0, 1, 0,
0, 0, 1, 0, 0,
0, 1, 0, 0, 0,
0, 1, 0, 0, 0,
0, 1, 0, 0, 0,
},
{ // 8
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
},
{ // 9
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 1, 0, 0, 1,
0, 1, 1, 1, 1,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
1, 1, 1, 1, 0,
},
}
type Image struct {
*image.NRGBA
color *color.NRGBA
width int //a digit width
height int //a digit height
dotsize int
}
func init() {
rand.Seed(int64(time.Second))
}
func NewImage(digits []byte, width, height int) *Image {
img := new(Image)
r := image.Rect(img.width, img.height, stdWidth, stdHeight)
img.NRGBA = image.NewNRGBA(r)
img.color = &color.NRGBA{
uint8(rand.Intn(129)),
uint8(rand.Intn(129)),
uint8(rand.Intn(129)),
0xFF,
}
// Draw background (10 random circles of random brightness)
img.calculateSizes(width, height, len(digits))
img.fillWithCircles(10, img.dotsize)
maxx := width - (img.width+img.dotsize)*len(digits) - img.dotsize
maxy := height - img.height - img.dotsize*2
x := rnd(img.dotsize*2, maxx)
y := rnd(img.dotsize*2, maxy)
// Draw digits.
for _, n := range digits {
img.drawDigit(font[n], x, y)
x += img.width + img.dotsize
}
// Draw strike-through line.
// 中间线不要
//img.strikeThrough()
return img
}
func (img *Image) WriteTo(w io.Writer) (int64, error) {
return 0, png.Encode(w, img)
}
func (img *Image) calculateSizes(width, height, ncount int) {
// Goal: fit all digits inside the image.
var border int
if width > height {
border = height / 5
} else {
border = width / 5
}
// Convert everything to floats for calculations.
w := float64(width - border*2) //268
h := float64(height - border*2) //48
// fw takes into account 1-dot spacing between digits.
fw := float64(fontWidth) + 1 //6
fh := float64(fontHeight) //8
nc := float64(ncount) //7
// Calculate the width of a single digit taking into account only the
// width of the image.
nw := w / nc //38
// Calculate the height of a digit from this width.
nh := nw * fh / fw //51
// Digit too high?
if nh > h {
// Fit digits based on height.
nh = h //nh = 44
nw = fw / fh * nh
}
// Calculate dot size.
img.dotsize = int(nh / fh)
// Save everything, making the actual width smaller by 1 dot to account
// for spacing between digits.
img.width = int(nw)
img.height = int(nh) - img.dotsize
}
func (img *Image) fillWithCircles(n, maxradius int) {
color := img.color
maxx := img.Bounds().Max.X
maxy := img.Bounds().Max.Y
for i := 0; i < n; i++ {
setRandomBrightness(color, 255)
r := rnd(1, maxradius)
img.drawCircle(color, rnd(r, maxx-r), rnd(r, maxy-r), r)
}
}
func (img *Image) drawHorizLine(color color.Color, fromX, toX, y int) {
for x := fromX; x <= toX; x++ {
img.Set(x, y, color)
}
}
func (img *Image) drawCircle(color color.Color, x, y, radius int) {
f := 1 - radius
dfx := 1
dfy := -2 * radius
xx := 0
yy := radius
img.Set(x, y+radius, color)
img.Set(x, y-radius, color)
img.drawHorizLine(color, x-radius, x+radius, y)
for xx < yy {
if f >= 0 {
yy--
dfy += 2
f += dfy
}
xx++
dfx += 2
f += dfx
img.drawHorizLine(color, x-xx, x+xx, y+yy)
img.drawHorizLine(color, x-xx, x+xx, y-yy)
img.drawHorizLine(color, x-yy, x+yy, y+xx)
img.drawHorizLine(color, x-yy, x+yy, y-xx)
}
}
func (img *Image) strikeThrough() {
r := 0
maxx := img.Bounds().Max.X
maxy := img.Bounds().Max.Y
y := rnd(maxy/3, maxy-maxy/3)
for x := 0; x < maxx; x += r {
r = rnd(1, img.dotsize/3)
y += rnd(-img.dotsize/2, img.dotsize/2)
if y <= 0 || y >= maxy {
y = rnd(maxy/3, maxy-maxy/3)
}
img.drawCircle(img.color, x, y, r)
}
}
func (img *Image) drawDigit(digit []byte, x, y int) {
skf := rand.Float64() * float64(rnd(-maxSkew, maxSkew))
xs := float64(x)
minr := img.dotsize / 2 // minumum radius
maxr := img.dotsize/2 + img.dotsize/4 // maximum radius
y += rnd(-minr, minr)
for yy := 0; yy < fontHeight; yy++ {
for xx := 0; xx < fontWidth; xx++ {
if digit[yy*fontWidth+xx] != blackChar {
continue
}
// Introduce random variations.
or := rnd(minr, maxr)
ox := x + (xx * img.dotsize) + rnd(0, or/2)
oy := y + (yy * img.dotsize) + rnd(0, or/2)
img.drawCircle(img.color, ox, oy, or)
}
xs += skf
x = int(xs)
}
}
func setRandomBrightness(c *color.NRGBA, max uint8) {
minc := min3(c.R, c.G, c.B)
maxc := max3(c.R, c.G, c.B)
if maxc > max {
return
}
n := rand.Intn(int(max-maxc)) - int(minc)
c.R = uint8(int(c.R) + n)
c.G = uint8(int(c.G) + n)
c.B = uint8(int(c.B) + n)
}
func min3(x, y, z uint8) (o uint8) {
o = x
if y < o {
o = y
}
if z < o {
o = z
}
return
}
func max3(x, y, z uint8) (o uint8) {
o = x
if y > o {
o = y
}
if z > o {
o = z
}
return
}
// rnd returns a random number in range [from, to].
func rnd(from, to int) int {
//println(to+1-from)
return rand.Intn(to+1-from) + from
}
const (
// Standard length of uniuri string to achive ~95 bits of entropy.
StdLen = 16
// Length of uniurl string to achive ~119 bits of entropy, closest
// to what can be losslessly converted to UUIDv4 (122 bits).
UUIDLen = 20
)
// Standard characters allowed in uniuri string.
var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// New returns a new random string of the standard length, consisting of
// standard characters.
func New() string {
return NewLenChars(StdLen, StdChars)
}
// NewLen returns a new random string of the provided length, consisting of
// standard characters.
func NewLen(length int) string {
return NewLenChars(length, StdChars)
}
// NewLenChars returns a new random string of the provided length, consisting
// of the provided byte slice of allowed characters (maximum 256).
func NewLenChars(length int, chars []byte) string {
b := make([]byte, length)
r := make([]byte, length+(length/4)) // storage for random bytes.
clen := byte(len(chars))
maxrb := byte(256 - (256 % len(chars)))
i := 0
for {
if _, err := io.ReadFull(crand.Reader, r); err != nil {
panic("error reading from random source: " + err.Error())
}
for _, c := range r {
if c >= maxrb {
// Skip this number to avoid modulo bias.
continue
}
b[i] = chars[c%clen]
i++
if i == length {
return string(b)
}
}
}
panic("unreachable")
}
func Fetch() (*Image, string) {
d := make([]byte, 4)
s := NewLen(4)
ss := ""
d = []byte(s)
for v := range d {
d[v] %= 10
ss += strconv.FormatInt(int64(d[v]), 32)
}
return NewImage(d, 100, 40), ss
}

26
app/lea/crypto.go Normal file
View File

@@ -0,0 +1,26 @@
// contains two cryptographic functions for both storing and comparing passwords.
package lea
import (
"golang.org/x/crypto/bcrypt"
)
// GenerateHash generates bcrypt hash from plaintext password
func GenerateHash(password string) ([]byte, error) {
hex := []byte(password)
hashedPassword, err := bcrypt.GenerateFromPassword(hex, 10)
if err != nil {
return hashedPassword, err
}
return hashedPassword, nil
}
// CompareHash compares bcrypt password with a plaintext one. Returns true if passwords match
// and false if they do not.
func CompareHash(digest []byte, password string) bool {
hex := []byte(password)
if err := bcrypt.CompareHashAndPassword(digest, hex); err == nil {
return true
}
return false
}

View File

@@ -24,29 +24,29 @@ import (
type Html2Image struct {
image *image.RGBA
gc *draw2d.ImageGraphicContext
// 试探
gc2 *draw2d.ImageGraphicContext
width float64 // 图片宽度
height float64
painWidth float64 // 画布宽度
startX float64
x float64
y float64
isFirstP bool // 是否是第一个段落?
// 换行和段落的高度
brY float64
pY float64
// 字体
normalFontFamily draw2d.FontData
boldFontFamily draw2d.FontData
// preTag 之前的标签
preTag *html.Node
}
@@ -58,23 +58,23 @@ func NewHtml2Image() *Html2Image {
i, gc := h.InitGc(h.width, h.height)
h.gc = gc;
h.image = i
// 试探
_, h.gc2 = h.InitGc(h.width, 100)
h.startX = 10
h.startX = 10
// 最初位置
h.x = h.startX
h.y = 80
h.isFirstP = true
h.normalFontFamily = draw2d.FontData{"xihei", 4, draw2d.FontStyleNormal};
h.boldFontFamily = draw2d.FontData{"heiti", 5, draw2d.FontStyleNormal};
h.SetNormalFont()
return h
}
@@ -86,12 +86,12 @@ func (this *Html2Image) InitGc(w, h float64) (* image.RGBA, *draw2d.ImageGraphic
gc.SetFillColor(image.White)
// fill the background
// gc.Clear()
draw2d.SetFontFolder(revel.BasePath + "/public/fonts/weibo")
draw2d.Rect(gc, 0, 0, w, h) // 设置背景
gc.FillStroke()
gc.SetFillColor(image.Black)
// 这个很耗时
// gc.Translate(0, 0)
return i, gc
@@ -101,7 +101,7 @@ func (this *Html2Image) SaveToPngFile(filePath string) bool {
// m := this.image;
m := this.image.SubImage(image.Rect(0, 0, int(this.width), int(this.y + 20)))
// 需要截断之
f, err := os.Create(filePath)
if err != nil {
return false
@@ -124,14 +124,14 @@ func (this *Html2Image) SaveToPngFile(filePath string) bool {
func (this *Html2Image) SetSmallFont() {
this.gc.SetFontData(this.normalFontFamily)
this.gc2.SetFontData(this.normalFontFamily)
this.gc.SetFillColor(color.NRGBA{60, 60, 60, 255})
this.gc.SetFontSize(12)
this.gc2.SetFontSize(12)
this.brY = 16
this.pY = 30
this.painWidth = this.width - 10
}
@@ -139,13 +139,13 @@ func (this *Html2Image) SetNormalFont() {
this.gc.SetFillColor(image.Black)
this.gc.SetFontData(this.normalFontFamily)
this.gc2.SetFontData(this.normalFontFamily)
this.gc.SetFontSize(14)
this.gc2.SetFontSize(14)
this.brY = 20
this.pY = 30
this.painWidth = this.width - 10
}
func (this *Html2Image) SetAColor() {
@@ -155,16 +155,16 @@ func (this *Html2Image) SetAColor() {
// 标题
func (this *Html2Image) SetTitleFont() {
this.gc.SetFillColor(image.Black)
this.gc.SetFontData(this.boldFontFamily)
this.gc2.SetFontData(this.boldFontFamily)
this.gc.SetFontSize(24)
this.gc2.SetFontSize(24)
this.brY = 30
this.pY = 60
this.painWidth = this.width - 100
}
@@ -175,9 +175,9 @@ func (this *Html2Image) SetHeadFont(h string) {
this.gc.SetFontData(this.boldFontFamily)
this.gc2.SetFontData(this.boldFontFamily)
this.painWidth = this.width - 50
if h == "h1" {
this.gc.SetFontSize(20)
this.gc2.SetFontSize(20)
@@ -229,7 +229,7 @@ func (this *Html2Image) IsOver(r []rune) bool {
// 以下的方法可以极大节约时间
// a, b, c, d := this.gc2.GetStringBounds(string(r))
// width2 := c - a + 2
// fmt.Println(width2)
// fmt.Println(c - a)
@@ -268,7 +268,7 @@ func (this *Html2Image) InsertText(text string, needTest bool, prefix string) {
this.InsertText(text, true, prefix)
return;
}
r := []rune(text)
// 试探吧, 可能需要截取
if !needTest || !this.IsOver(r) {
@@ -316,17 +316,17 @@ func (this *Html2Image) InsertText(text string, needTest bool, prefix string) {
end = i
}
}
// 这一段写上
// println("------>" + string(r[0:end]))
// 这里, 判断后面一个是否是标点符号
end = this.includePunctuation(r, end)
this.InsertText(string(r[0:end]), false, prefix)
this.NewBr()
// 之后的
this.InsertText(string(r[end:]), true, prefix)
return;
} else {
// 没超出, 不用计算, 但出要看是否是结尾了
@@ -351,17 +351,17 @@ func (this *Html2Image) InsertText(text string, needTest bool, prefix string) {
end = maxRI + 1
}
}
// 这一段写上
// println("-e----->" + string(r[0:end]))
// 这里, 判断后面一个是否是标点符号
end = this.includePunctuation(r, end)
this.InsertText(string(r[0:end]), false, prefix)
this.NewBr()
// 之后的
this.InsertText(string(r[end:]), true, prefix)
return;
}
}
@@ -376,9 +376,9 @@ func (this *Html2Image) SetBottom(username, url string) {
this.gc.SetStrokeColor(color.NRGBA{200, 0, 0, 255})
this.gc.SetLineWidth(2)
this.gc.FillStroke()
this.SetSmallFont()
// 左侧写字
this.NewP()
this.InsertText("本文来自 " + username + " 的leanote笔记", true, " ")
@@ -389,11 +389,11 @@ func (this *Html2Image) SetBottom(username, url string) {
siteUrl = "http://leanote.com"
}
this.InsertA(siteUrl + "/blog/" + username, false)
this.setLogo()
// this.painWidth = this.width - 100
// this.NewP()
// this.InsertText("leanote, 不一样的笔记.", false, " ")
// this.InsertText("leanote, 不一样的笔记.", false, " ")
// this.NewBr()
// this.InsertText("在这里你可以管理自己的知识", false, " ")
// this.NewBr()
@@ -401,7 +401,7 @@ func (this *Html2Image) SetBottom(username, url string) {
// this.NewBr()
// this.InsertText("并且还可以将笔记设为博客公开", false, " ")
// this.InsertText(". 赶紧加入吧! leanote.com", false, "")
//
//
// Logo
}
@@ -411,7 +411,7 @@ func (this *Html2Image) setImage(path string, x, y float64) {
return;
panic(err)
}
var m1 image.Image
_, ext := lea.SplitFilename(path)
if ext == ".png" {
@@ -424,8 +424,8 @@ func (this *Html2Image) setImage(path string, x, y float64) {
if err != nil {
return
panic(err)
}
}
this.gc.Translate(x, y)
this.gc.DrawImage(m1)
this.gc.Translate(-x, -y)
@@ -448,10 +448,10 @@ func (this *Html2Image) InsertA(text string, isNormal bool) {
if text == "" {
return
}
this.SetAColor()
this.InsertText(text, true, "")
// 还原
if isNormal {
this.SetNormalFont()
@@ -464,17 +464,17 @@ func (this *Html2Image) InsertA(text string, isNormal bool) {
func (this *Html2Image) InsertTitle(title string) {
oldX := this.x
oldY := this.y - 35
// 插入之
this.SetTitleFont()
this.InsertText(title, true, " ")
// 还原字体大小
this.SetNormalFont()
this.NewBr()
this.gc.MoveTo(oldX, oldY)
this.gc.LineTo(this.x, this.y - 10)
this.gc.SetStrokeColor(color.NRGBA{200, 0, 0, 255})
@@ -512,7 +512,7 @@ func (this *Html2Image) InsertCode(n *html.Node) {
}
}
this.NewBr()
this.gc.MoveTo(oldX, oldY)
this.gc.LineTo(this.x, this.y - 20)
this.gc.SetStrokeColor(color.NRGBA{0, 200, 0, 255})
@@ -521,14 +521,14 @@ func (this *Html2Image) InsertCode(n *html.Node) {
}
// 插入图片
// 这个path应该是url,
// 这个path应该是url,
// http://abc.com/a.gif 需要先下载
// 或 /upload/a.gif
func (this *Html2Image) InsertImage(path string, needTrans bool, width uint) {
if path == "" {
return;
}
// 是url, 那么取网络图片之
var ok bool
if strings.HasPrefix(path, "http") || strings.HasPrefix(path, "//") {
@@ -539,7 +539,7 @@ func (this *Html2Image) InsertImage(path string, needTrans bool, width uint) {
} else {
path = revel.BasePath + "/public/" + path
}
// 需要转换, logo不需要转换
if(needTrans) {
painWidth := uint(this.painWidth - 10)
@@ -557,7 +557,7 @@ func (this *Html2Image) InsertImage(path string, needTrans bool, width uint) {
return;
panic(err)
}
var m1 image.Image
_, ext := lea.SplitFilename(path)
if ext == ".png" {
@@ -568,8 +568,8 @@ func (this *Html2Image) InsertImage(path string, needTrans bool, width uint) {
if err != nil {
return
panic(err)
}
}
// 如果之前是p, 那么不要有<br>
if this.preTag.Data != "p" {
this.NewBr()
@@ -580,9 +580,9 @@ func (this *Html2Image) InsertImage(path string, needTrans bool, width uint) {
this.gc.Translate(-this.x, -this.y) // 这个有用些
this.y += float64(m1.Bounds().Dy()) - 20
this.NewP()
os.Remove(path)
// 如果图片是文章第一个的话, 之后的需要p
this.isFirstP = false
}
@@ -605,7 +605,7 @@ func (this *Html2Image) InsertBody(htmlStr string) (ok bool) {
this.preTag = n
}
}()
// 标签
if n.Type == html.ElementNode {
if n.Data == "p" {
@@ -616,7 +616,7 @@ func (this *Html2Image) InsertBody(htmlStr string) (ok bool) {
}
return;
}
// 也是一个段落, 只是要缩进
if n.Data == "ul" || n.Data == "ol" {
this.NewP()
@@ -637,7 +637,7 @@ func (this *Html2Image) InsertBody(htmlStr string) (ok bool) {
} else {
f(c, n, "")
}
if c.Type == html.ElementNode {
if c.Data == "br" || c.Data == "p" {
needPrefix = true
@@ -650,19 +650,19 @@ func (this *Html2Image) InsertBody(htmlStr string) (ok bool) {
this.NewBr()
return;
}
// 标题
if n.Data == "h1" || n.Data == "h2" || n.Data == "h3" || n.Data == "h4" {
this.InsertHead(n)
return;
}
if n.Data == "pre" {
// 把之后的全拿过来
this.InsertCode(n)
return;
}
// 图片
// 得到src
if n.Data == "img" {
@@ -680,22 +680,22 @@ func (this *Html2Image) InsertBody(htmlStr string) (ok bool) {
}
return;
}
// 链接
// 如果链接里只有文本, 那么单独处理, 如果还有其它的, 不作链接处理
if n.Data == "a" {
if n.FirstChild == n.LastChild {
this.InsertA(n.FirstChild.Data, true)
return;
}
}
}
// 空行
if n.Data == "br" { // || n.Data == "div"
this.NewBr()
}
}
// 是文本, 输出之
if n.Type == html.TextNode {
data := strings.TrimSpace(n.Data);
@@ -705,36 +705,36 @@ func (this *Html2Image) InsertBody(htmlStr string) (ok bool) {
}
return;
}
// 其余的
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c, n, prefix)
}
return;
}
f(doc, nil, "")
return true
}
// 主函数
func ToImage(uid, username, noteId, title, htmlStr, toPath string) (ok bool) {
h := NewHtml2Image()
// 标题
h.InsertTitle(title)
// 主体
ok = h.InsertBody(htmlStr)
if(!ok) {
return
}
// 页眉与页脚
h.SetBottom(username, "")
// 保存成png图片
ok = h.SaveToPngFile(toPath)
return
@@ -757,4 +757,4 @@ func TestFillString() {
func ToImage(uid, username, noteId, title, htmlStr, toPath string) (ok bool) {
return false
}
}

View File

@@ -0,0 +1,9 @@
package html2image
import (
"github.com/leanote/leanote/app/info"
)
func Html2Image(userInfo info.User, note info.Note, content, toPath string) bool {
return true
}

223
app/lea/i18n/i18n.go Normal file
View File

@@ -0,0 +1,223 @@
package i18n
import (
"fmt"
"github.com/revel/revel"
"github.com/robfig/config"
"os"
"path/filepath"
"regexp"
"strings"
)
const (
CurrentLocaleViewArg = "currentLocale" // The key for the current locale render arg value
messageFilesDirectory = "messages"
messageFilePattern = `^\w+\.conf$`
unknownValueFormat = "??? %s ???"
defaultLanguageOption = "i18n.default_language"
localeCookieConfigKey = "i18n.cookie"
)
var (
// All currently loaded message configs.
// en-us, zh-cn, zh-hk ->
messages map[string]*config.Config
)
func GetAllLangMessages() map[string]*config.Config {
return messages
}
func HasLang(lang string) bool {
_, ok := messages[lang]
return ok
}
func GetDefaultLang() string {
lang, _ := revel.Config.String(defaultLanguageOption)
return lang
}
// Return all currently loaded message languages.
func MessageLanguages() []string {
languages := make([]string, len(messages))
i := 0
for language, _ := range messages {
languages[i] = language
i++
}
return languages
}
// Perform a message look-up for the given locale and message using the given arguments.
//
// When either an unknown locale or message is detected, a specially formatted string is returned.
func Message(locale, message string, args ...interface{}) string {
language, region := parseLocale(locale)
langAndRegion := language + "-" + region
// revel.TRACE.Println(langAndRegion + " 怎么回事")
messageConfig, knownLanguage := messages[langAndRegion]
if !knownLanguage {
// revel.TRACE.Printf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message)
if defaultLanguage, found := revel.Config.String(defaultLanguageOption); found {
// revel.TRACE.Printf("Using default language '%s'", defaultLanguage)
messageConfig, knownLanguage = messages[defaultLanguage]
if !knownLanguage {
// WARN.Printf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message)
return fmt.Sprintf(unknownValueFormat, message)
}
} else {
// WARN.Printf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption)
return fmt.Sprintf(unknownValueFormat, message)
}
}
// This works because unlike the goconfig documentation suggests it will actually
// try to resolve message in DEFAULT if it did not find it in the given section.
value, error := messageConfig.String(region, message)
if error != nil {
// WARN.Printf("Unknown message '%s' for locale '%s'", message, locale)
return fmt.Sprintf(unknownValueFormat, message)
}
if len(args) > 0 {
// revel.TRACE.Printf("Arguments detected, formatting '%s' with %v", value, args)
value = fmt.Sprintf(value, args...)
}
return value
}
func parseLocale(locale string) (language, region string) {
if strings.Contains(locale, "-") {
languageAndRegion := strings.Split(locale, "-")
return languageAndRegion[0], languageAndRegion[1]
}
return locale, ""
}
// Recursively read and cache all available messages from all message files on the given path.
func loadMessages(path string) {
messages = make(map[string]*config.Config)
if error := filepath.Walk(path, loadEachMessageLang); error != nil && !os.IsNotExist(error) {
// ERROR.Println("Error reading messages files:", error)
}
}
// 加载每一个文件夹
func loadEachMessageLang(parentPath string, parentInfo os.FileInfo, osError error) (err error) {
if !parentInfo.IsDir() {
return nil
}
if err := filepath.Walk(parentPath, func(path string, info os.FileInfo, osError error) error {
return loadMessageFile(parentInfo.Name(), path, info, osError)
}); err != nil && !os.IsNotExist(err) {
// ERROR.Println("Error reading messages files:", error)
}
return err
}
// Load a single message file
func loadMessageFile(locale string, path string, info os.FileInfo, osError error) error {
if osError != nil {
return osError
}
if info.IsDir() {
return nil
}
if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched {
if config, error := parseMessagesFile(path); error != nil {
return error
} else {
// locale := parseLocaleFromFileName(info.Name())
// revel.TRACE.Print(locale + "----locale")
// If we have already parsed a message file for this locale, merge both
if _, exists := messages[locale]; exists {
messages[locale].Merge(config)
revel.TRACE.Printf("Successfully merged messages for locale '%s'", locale)
} else {
messages[locale] = config
}
revel.TRACE.Println("Successfully loaded messages from file", info.Name())
}
} else {
revel.TRACE.Printf("Ignoring file %s because it did not have a valid extension", info.Name())
}
return nil
}
func parseMessagesFile(path string) (messageConfig *config.Config, error error) {
messageConfig, error = config.ReadDefault(path)
return
}
func parseLocaleFromFileName(file string) string {
extension := filepath.Ext(file)[1:]
return strings.ToLower(extension)
}
func init() {
revel.OnAppStart(func() {
loadMessages(filepath.Join(revel.BasePath, messageFilesDirectory))
})
}
func I18nFilter(c *revel.Controller, fc []revel.Filter) {
if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie {
revel.TRACE.Printf("Found locale cookie value: %s", cookieValue)
setCurrentLocaleControllerArguments(c, cookieValue)
} else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader {
revel.TRACE.Printf("Found Accept-Language header value: %s", headerValue)
setCurrentLocaleControllerArguments(c, headerValue)
} else {
revel.TRACE.Println("Unable to find locale in cookie or header, using empty string")
setCurrentLocaleControllerArguments(c, "")
}
fc[0](c, fc[1:])
}
// Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale.
func setCurrentLocaleControllerArguments(c *revel.Controller, locale string) {
c.Request.Locale = locale
c.ViewArgs[CurrentLocaleViewArg] = locale
}
// Determine whether the given request has valid Accept-Language value.
//
// Assumes that the accept languages stored in the request are sorted according to quality, with top
// quality first in the slice.
func hasAcceptLanguageHeader(request *revel.Request) (bool, string) {
if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 {
return true, request.AcceptLanguages[0].Language
}
return false, ""
}
// Determine whether the given request has a valid language cookie value.
func hasLocaleCookie(request *revel.Request) (bool, string) {
if request != nil && request.Cookies() != nil {
name := revel.Config.StringDefault(localeCookieConfigKey, revel.CookiePrefix+"_LANG")
if cookie, error := request.Cookie(name); error == nil {
return true, cookie.Value
} else {
revel.TRACE.Printf("Unable to read locale cookie with name '%s': %s", name, error.Error())
}
}
return false, ""
}

View File

@@ -1,26 +0,0 @@
package memcache
import (
"github.com/robfig/gomemcache/memcache"
"encoding/json"
)
func Set(key string, value map[string]string, expiration int32) {
// 把value转成byte
bytes, _ := json.Marshal(value)
if expiration == -1 {
expiration = 30 * 24 * 60 * 60 // 30天
}
client.Set(&memcache.Item{Key: key, Value: bytes, Expiration: expiration})
}
func Get(key string) map[string]string {
item, err := client.Get(key)
if err != nil {
return nil
}
m := map[string]string{}
json.Unmarshal(item.Value, &m)
return m
}

View File

@@ -1,11 +0,0 @@
package memcache
import (
"github.com/robfig/gomemcache/memcache"
)
var client *memcache.Client
func init() {
// client = memcache.New("localhost:11211")
}

View File

@@ -1,11 +1,13 @@
package netutil
import (
"strings"
"os"
// "path/filepath"
"net/http"
"io/ioutil"
"strings"
// "path/filepath"
. "github.com/leanote/leanote/app/lea"
"io/ioutil"
"net"
"net/http"
)
// net的util
@@ -13,38 +15,35 @@ import (
// toPath 文件保存的目录
// 默认是/tmp
// 返回文件的完整目录
func WriteUrl(url string, toPath string) (path string, ok bool) {
func WriteUrl(url string, toPath string) (length int64, newFilename, path string, ok bool) {
if url == "" {
return;
return
}
content, err := GetContent(url)
if err != nil {
return;
return
}
length = int64(len(content))
// a.html?a=a11&xxx
url = trimQueryParams(url)
_, ext := SplitFilename(url)
if toPath == "" {
toPath = "/tmp"
}
// dir := filepath.Dir(toPath)
newFilename := NewGuid() + ext
// dir := filepath.Dir(toPath)
newFilename = NewGuid() + ext
fullPath := toPath + "/" + newFilename
/*
if err := os.MkdirAll(dir, 0777); err != nil {
return
}
*/
// 写到文件中
file, err := os.Create(fullPath)
defer file.Close()
if err != nil {
return
defer file.Close()
if err != nil {
return
}
file.Write(content)
path = fullPath
ok = true
return
@@ -54,40 +53,51 @@ func WriteUrl(url string, toPath string) (path string, ok bool) {
func GetContent(url string) (content []byte, err error) {
var resp *http.Response
resp, err = http.Get(url)
if(resp != nil && resp.Body != nil) {
Log(err)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
} else {
}
if resp == nil || resp.Body == nil || err != nil || resp.StatusCode != http.StatusOK {
return
}
var buf []byte
buf, err = ioutil.ReadAll(resp.Body)
if(err != nil) {
if resp == nil || resp.Body == nil || err != nil || resp.StatusCode != http.StatusOK {
return
}
content = buf;
err = nil
return
var buf []byte
buf, err = ioutil.ReadAll(resp.Body)
if err != nil {
Log(err)
return
}
content = buf
err = nil
return
}
// 将url ?, #后面的字符串去掉
func trimQueryParams(url string) string {
pos := strings.Index(url, "?");
pos := strings.Index(url, "?")
if pos != -1 {
url = Substr(url, 0, pos);
url = Substr(url, 0, pos)
}
pos = strings.Index(url, "#");
pos = strings.Index(url, "#")
if pos != -1 {
url = Substr(url, 0, pos);
url = Substr(url, 0, pos)
}
pos = strings.Index(url, "!");
pos = strings.Index(url, "!")
if pos != -1 {
url = Substr(url, 0, pos);
url = Substr(url, 0, pos)
}
return url;
}
return url
}
// 通过domain得到ip
func GetIpFromDomain(domain string) string {
ip, _ := net.LookupIP(domain)
if ip != nil && len(ip) > 0 {
return ip[0].String()
}
return ""
}

87
app/lea/route/Route.go Normal file
View File

@@ -0,0 +1,87 @@
package route
import (
"github.com/leanote/leanote/app/db"
"github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea"
"net/url"
"strings"
)
// overwite revel RouterFilter
// /api/user/Info => ApiUser.Info()
var staticPrefix = []string{"/public", "/favicon.ico", "/css", "/js", "/images", "/tinymce", "/upload", "/fonts"}
func RouterFilter(c *revel.Controller, fc []revel.Filter) {
// 补全controller部分
path := c.Request.Request.URL.Path
// Figure out the Controller/Action
var route *revel.RouteMatch = revel.MainRouter.Route(c.Request.Request)
if route == nil {
c.Result = c.NotFound("No matching route found: " + c.Request.RequestURI)
return
}
// The route may want to explicitly return a 404.
if route.Action == "404" {
c.Result = c.NotFound("(intentionally)")
return
}
//----------
// life start
/*
type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port
Path string
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
}
*/
if route.ControllerName != "Static" {
// 检查mongodb 是否lost
db.CheckMongoSessionLost()
// api设置
// leanote.com/api/user/get => ApiUser::Get
//* /api/login ApiAuth.Login, 这里的设置, 其实已经转成了ApiAuth了
if strings.HasPrefix(path, "/api") && !strings.HasPrefix(route.ControllerName, "Api") {
route.ControllerName = "Api" + route.ControllerName
} else if strings.HasPrefix(path, "/member") && !strings.HasPrefix(route.ControllerName, "Member") {
// member设置
route.ControllerName = "Member" + route.ControllerName
}
// end
}
// Set the action.
if err := c.SetAction(route.ControllerName, route.MethodName); err != nil {
c.Result = c.NotFound(err.Error())
return
}
// Add the route and fixed params to the Request Params.
c.Params.Route = route.Params
// Add the fixed parameters mapped by name.
// TODO: Pre-calculate this mapping.
for i, value := range route.FixedParams {
if c.Params.Fixed == nil {
c.Params.Fixed = make(url.Values)
}
if i < len(c.MethodType.Args) {
arg := c.MethodType.Args[i]
c.Params.Fixed.Set(arg.Name, value)
} else {
revel.WARN.Println("Too many parameters to", route.Action, "trying to add", value)
break
}
}
fc[0](c, fc[1:])
}

View File

@@ -1,31 +0,0 @@
package session
import (
"github.com/robfig/revel"
"leanote/app/lea/memcache"
// . "leanote/app/lea"
)
// 使用filter
// 很巧妙就使用了memcache来处理session
// revel的session(cookie)只存sessionId, 其它信息存在memcache中
func SessionFilter(c *revel.Controller, fc []revel.Filter) {
sessionId := c.Session.Id()
// 从memcache中得到cache, 赋给session
cache := revel.Session(memcache.Get(sessionId))
if cache == nil {
cache = revel.Session{}
cache.Id()
}
c.Session = cache
fc[0](c, fc[1:])
// 再把session保存之
memcache.Set(sessionId, c.Session, -1)
// 只留下sessionId
c.Session = revel.Session{revel.SESSION_ID_KEY: sessionId}
}

View File

@@ -2,11 +2,13 @@ package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"io/ioutil"
"strings"
// "time"
)
/*
用golang exec 总是说找不到uglifyjs命令, 需要全部路径
而且node, npm要在/usr/bin下, 已建ln
@@ -29,15 +31,17 @@ import (
*/
//var jss = []string{"js/jquery-cookie", "js/bootstrap"}
var jss = []string{"js/jquery-cookie", "js/bootstrap",
"js/common", "js/app/note", "js/app/tag", "js/app/notebook", "js/app/share",
var jss = []string{"js/jquery-cookie", "js/bootstrap",
"js/common", "js/app/note", "js/app/tag", "js/app/notebook", "js/app/share",
"js/object_id", "js/ZeroClipboard/ZeroClipboard"}
var base = "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/public/"
var base1 = "/Users/life/Documents/Go/package2/src/github.com/leanote/leanote/"
var base = "/Users/life/Documents/Go/package2/src/github.com/leanote/leanote/public/"
var cmdPath = "/usr/local/bin/uglifyjs"
func cmdError(err error) {
if err != nil {
fmt.Println(err)
fmt.Fprintf(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, "", "")
} else {
fmt.Println("OK")
@@ -50,25 +54,26 @@ func compressJs(filename string) {
to := base + filename + "-min.js"
cmd := exec.Command(cmdPath, source, "-o", to)
_, err := cmd.CombinedOutput()
fmt.Println(source)
cmdError(err)
}
func combineJs() {
// 生成一个总文件
cmd := exec.Command("rm", base + "js/all.js")
cmd := exec.Command("rm", base+"js/all.js")
_, err := cmd.CombinedOutput()
cmdError(err)
for _, js := range jss {
to := base + js + "-min.js"
fmt.Println(to)
compressJs(js)
// 每个压缩后的文件放入之
cmd2 := exec.Command("/bin/sh", "-c", "cat " + to + " >> " + base + "js/all.js")
cmd2 := exec.Command("/bin/sh", "-c", "cat "+to+" >> "+base+"js/all.js")
_, err := cmd2.CombinedOutput()
cmdError(err)
cmd2 = exec.Command("/bin/sh", "-c", "cat \n >> " + base + "js/all.js")
cmd2 = exec.Command("/bin/sh", "-c", "cat \n >> "+base+"js/all.js")
_, err = cmd2.CombinedOutput()
cmdError(err)
}
@@ -77,65 +82,78 @@ func combineJs() {
// 改note-dev->note
func dev() {
// 即替换note.js->note-min.js
m := map[string]string{"note.js": "note-min.js",
"app.js": "app-min.js",
"page.js": "page-min.js",
"common.js": "common-min.js",
"notebook.js": "notebook-min.js",
"share.js": "share-min.js",
"tag.js": "tag-min.js",
"jquery.contextmenu.js": "jquery.contextmenu-min.js",
"editor/editor.js": "editor/editor-min.js",
m := map[string]string{"tinymce.dev.js": "tinymce.min.js",
"tinymce.js": "tinymce.min.js",
"jquery.ztree.all-3.5.js": "jquery.ztree.all-3.5-min.js",
"note.js": "note-min.js",
"app.js": "app-min.js",
"page.js": "page-min.js",
"common.js": "common-min.js",
"notebook.js": "notebook-min.js",
"share.js": "share-min.js",
"tag.js": "tag-min.js",
"jquery.slimscroll.js": "jquery.slimscroll-min.js",
"jquery.contextmenu.js": "jquery.contextmenu-min.js",
"editor/editor.js": "editor/editor-min.js",
"/public/mdeditor/editor/scrollLink.js": "/public/mdeditor/editor/scrollLink-min.js",
"console.log(o);": "",
}
path := "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/src/views/note/note-dev.html"
target := "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/src/views/note/note.html"
"console.log(o);": "",
}
path := base1 + "/src/views/note/note-dev.html"
target := base1 + "/src/views/note/note.html"
bs, _ := ioutil.ReadFile(path)
content := string(bs)
print(content)
for key, value := range m {
content = strings.Replace(content, key, value, -1)
}
// var time = time.Now().Unix() % 1000
// content = strings.Replace(content, "-min.js", fmt.Sprintf("-min.js?r=%d", time), -1)
// content = strings.Replace(content, "default{{end}}.css", fmt.Sprintf("default{{end}}.css?r=%d", time), 1)
// content = strings.Replace(content, "writting-overwrite.css", fmt.Sprintf("writting-overwrite.css?r=%d", time), 1)
ioutil.WriteFile(target, []byte(content), os.ModeAppend)
}
// 压缩js成一块
func tinymce() {
// cmdStr := "node_modules/jake/bin/cli.js minify bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]"
// cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify", "bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]")
cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify")
cmd.Dir = "/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master"
_, err := cmd.CombinedOutput()
cmdError(err)
// cmdStr := "node_modules/jake/bin/cli.js minify bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]"
// cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify", "bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]")
cmd := exec.Command("/bin/sh", "-c", "grunt minify")
cmd.Dir = base + "/tinymce_4.1.9"
fmt.Println("正在build tinymce")
// 必须要先删除
cmd2 := exec.Command("/bin/sh", "-c", "rm "+cmd.Dir+"/js/tinymce/tinymce.dev.js")
cmd2.CombinedOutput()
cmd2 = exec.Command("/bin/sh", "-c", "rm "+cmd.Dir+"/js/tinymce/tinymce.jquery.dev.js")
c, _ := cmd2.CombinedOutput()
fmt.Println(string(c))
c, _ = cmd.CombinedOutput()
fmt.Println(string(c))
}
func main() {
dev();
// 压缩tinymce
// tinymce()
dev()
// 其它零散的需要压缩的js
otherJss := []string{"tinymce/tinymce", "js/app/page", "js/contextmenu/jquery.contextmenu",
"mdeditor/editor/scrollLink",
"mdeditor/editor/editor",
"mdeditor/editor/jquery.waitforimages",
"mdeditor/editor/pagedown/local/Markdown.local.zh",
"mdeditor/editor/pagedown/Markdown.Editor",
"mdeditor/editor/pagedown/Markdown.Sanitizer",
"mdeditor/editor/pagedown/Markdown.Converter",
"mdeditor/editor/Markdown.Extra",
"mdeditor/editor/underscore",
"mdeditor/editor/mathJax",
otherJss := []string{"js/main", "js/app/page", "js/contextmenu/jquery.contextmenu",
"js/jquery.ztree.all-3.5",
"js/jQuery-slimScroll-1.3.0/jquery.slimscroll",
}
}
for _, js := range otherJss {
compressJs(js)
}
// 先压缩后合并
combineJs()
// 压缩tinymce
tinymce()
}

View File

@@ -2,7 +2,7 @@ package service
import (
"github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
// . "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/db"
"gopkg.in/mgo.v2/bson"
"time"
@@ -30,9 +30,9 @@ func (this *AlbumService) GetAlbums(userId string) []info.Album {
// delete album
// presupposition: has no images under this ablum
func (this *AlbumService) DeleteAlbum(userId, albumId string) (bool, string) {
if db.Count(db.Files, bson.M{"AlbumId": bson.ObjectIdHex(albumId),
if db.Count(db.Files, bson.M{"AlbumId": bson.ObjectIdHex(albumId),
"UserId": bson.ObjectIdHex(userId),
}) == 0 {
}) == 0 {
return db.DeleteByIdAndUserId(db.Albums, albumId, userId), ""
}
return false, "has images"
@@ -41,4 +41,4 @@ func (this *AlbumService) DeleteAlbum(userId, albumId string) (bool, string) {
// update album name
func (this *AlbumService) UpdateAlbum(albumId, userId, name string) bool {
return db.UpdateByIdAndUserIdField(db.Albums, albumId, userId, "Name", name)
}
}

View File

@@ -0,0 +1,248 @@
package service
import (
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
"os"
"strings"
"time"
)
type AttachService struct {
}
// add attach
// api调用时, 添加attach之前是没有note的
// fromApi表示是api添加的, updateNote传过来的, 此时不要incNote's usn, 因为updateNote会inc的
func (this *AttachService) AddAttach(attach info.Attach, fromApi bool) (ok bool, msg string) {
attach.CreatedTime = time.Now()
ok = db.Insert(db.Attachs, attach)
note := noteService.GetNoteById(attach.NoteId.Hex())
// api调用时, 添加attach之前是没有note的
var userId string
if note.NoteId != "" {
userId = note.UserId.Hex()
} else {
userId = attach.UploadUserId.Hex()
}
if ok {
// 更新笔记的attachs num
this.updateNoteAttachNum(attach.NoteId, 1)
}
if !fromApi {
// 增长note's usn
noteService.IncrNoteUsn(attach.NoteId.Hex(), userId)
}
return
}
// 更新笔记的附件个数
// addNum 1或-1
func (this *AttachService) updateNoteAttachNum(noteId bson.ObjectId, addNum int) bool {
num := db.Count(db.Attachs, bson.M{"NoteId": noteId})
/*
note := info.Note{}
note = noteService.GetNoteById(noteId.Hex())
note.AttachNum += addNum
if note.AttachNum < 0 {
note.AttachNum = 0
}
Log(note.AttachNum)
*/
return db.UpdateByQField(db.Notes, bson.M{"_id": noteId}, "AttachNum", num)
}
// list attachs
func (this *AttachService) ListAttachs(noteId, userId string) []info.Attach {
attachs := []info.Attach{}
// 判断是否有权限为笔记添加附件, userId为空时表示是分享笔记的附件
if userId != "" && !shareService.HasUpdateNotePerm(noteId, userId) {
return attachs
}
// 笔记是否是自己的
note := noteService.GetNoteByIdAndUserId(noteId, userId)
if note.NoteId == "" {
return attachs
}
// TODO 这里, 优化权限控制
db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs)
return attachs
}
// api调用, 通过noteIds得到note's attachs, 通过noteId归类返回
func (this *AttachService) getAttachsByNoteIds(noteIds []bson.ObjectId) map[string][]info.Attach {
attachs := []info.Attach{}
db.ListByQ(db.Attachs, bson.M{"NoteId": bson.M{"$in": noteIds}}, &attachs)
noteAttchs := make(map[string][]info.Attach)
for _, attach := range attachs {
noteId := attach.NoteId.Hex()
if itAttachs, ok := noteAttchs[noteId]; ok {
noteAttchs[noteId] = append(itAttachs, attach)
} else {
noteAttchs[noteId] = []info.Attach{attach}
}
}
return noteAttchs
}
func (this *AttachService) UpdateImageTitle(userId, fileId, title string) bool {
return db.UpdateByIdAndUserIdField(db.Files, fileId, userId, "Title", title)
}
// Delete note to delete attas firstly
func (this *AttachService) DeleteAllAttachs(noteId, userId string) bool {
note := noteService.GetNoteById(noteId)
if note.UserId.Hex() == userId {
attachs := []info.Attach{}
db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs)
for _, attach := range attachs {
attach.Path = strings.TrimLeft(attach.Path, "/")
os.Remove(revel.BasePath + "/" + attach.Path)
}
return true
}
return false
}
// delete attach
// 删除附件为什么要incrNoteUsn ? 因为可能没有内容要修改的
func (this *AttachService) DeleteAttach(attachId, userId string) (bool, string) {
attach := info.Attach{}
db.Get(db.Attachs, attachId, &attach)
if attach.AttachId != "" {
// 判断是否有权限为笔记添加附件
if !shareService.HasUpdateNotePerm(attach.NoteId.Hex(), userId) {
return false, "No Perm"
}
if db.Delete(db.Attachs, bson.M{"_id": bson.ObjectIdHex(attachId)}) {
this.updateNoteAttachNum(attach.NoteId, -1)
attach.Path = strings.TrimLeft(attach.Path, "/")
err := os.Remove(revel.BasePath + "/" + attach.Path)
if err == nil {
// userService.UpdateAttachSize(note.UserId.Hex(), -attach.Size)
// 修改note Usn
noteService.IncrNoteUsn(attach.NoteId.Hex(), userId)
return true, "delete file success"
}
return false, "delete file error"
}
return false, "db error"
}
return false, "no such item"
}
// 获取文件路径
// 要判断是否具有权限
// userId是否具有attach的访问权限
func (this *AttachService) GetAttach(attachId, userId string) (attach info.Attach) {
if attachId == "" {
return
}
attach = info.Attach{}
db.Get(db.Attachs, attachId, &attach)
path := attach.Path
if path == "" {
return
}
note := noteService.GetNoteById(attach.NoteId.Hex())
// 判断权限
// 笔记是否是公开的
if note.IsBlog {
return
}
// 笔记是否是我的
if note.UserId.Hex() == userId {
return
}
// 我是否有权限查看或协作
if shareService.HasReadNotePerm(attach.NoteId.Hex(), userId) {
return
}
attach = info.Attach{}
return
}
// 复制笔记时需要复制附件
// noteService调用, 权限已判断
func (this *AttachService) CopyAttachs(noteId, toNoteId, toUserId string) bool {
attachs := []info.Attach{}
db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs)
// 复制之
toNoteIdO := bson.ObjectIdHex(toNoteId)
for _, attach := range attachs {
attach.AttachId = ""
attach.NoteId = toNoteIdO
// 文件复制一份
_, ext := SplitFilename(attach.Name)
newFilename := NewGuid() + ext
dir := "files/" + toUserId + "/attachs"
filePath := dir + "/" + newFilename
err := os.MkdirAll(revel.BasePath+"/"+dir, 0755)
if err != nil {
return false
}
_, err = CopyFile(revel.BasePath+"/"+attach.Path, revel.BasePath+"/"+filePath)
if err != nil {
return false
}
attach.Name = newFilename
attach.Path = filePath
this.AddAttach(attach, false)
}
return true
}
// 只留下files的数据, 其它的都删除
func (this *AttachService) UpdateOrDeleteAttachApi(noteId, userId string, files []info.NoteFile) bool {
// 现在数据库内的
attachs := this.ListAttachs(noteId, userId)
nowAttachs := map[string]bool{}
if files != nil {
for _, file := range files {
if file.IsAttach && file.FileId != "" {
nowAttachs[file.FileId] = true
}
}
}
for _, attach := range attachs {
fileId := attach.AttachId.Hex()
if !nowAttachs[fileId] {
// 需要删除的
// TODO 权限验证去掉
this.DeleteAttach(fileId, userId)
}
}
return false
}

View File

@@ -2,21 +2,31 @@ package service
import (
"gopkg.in/mgo.v2/bson"
// "github.com/leanote/leanote/app/db"
// "github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
"github.com/revel/revel"
. "github.com/leanote/leanote/app/lea"
// "github.com/revel/revel"
"errors"
"fmt"
. "github.com/leanote/leanote/app/lea"
"strconv"
"strings"
)
// 登录与权限
// 登录与权限 Login & Register
type AuthService struct {
}
// pwd已md5了
func (this *AuthService) Login(emailOrUsername, pwd string) info.User {
return userService.LoginGetUserInfo(emailOrUsername, Md5(pwd))
// 使用bcrypt认证或者Md5认证
// Use bcrypt (Md5 depreciated)
func (this *AuthService) Login(emailOrUsername, pwd string) (info.User, error) {
emailOrUsername = strings.Trim(emailOrUsername, " ")
// pwd = strings.Trim(pwd, " ")
userInfo := userService.GetUserInfoByName(emailOrUsername)
if userInfo.UserId == "" || !ComparePwd(pwd, userInfo.Pwd) {
return userInfo, errors.New("wrong username or password")
}
return userInfo, nil
}
// 注册
@@ -30,57 +40,79 @@ func (this *AuthService) Login(emailOrUsername, pwd string) info.User {
// 1. 添加用户
// 2. 将leanote共享给我
// [ok]
func (this *AuthService) Register(email, pwd string) (bool, string) {
func (this *AuthService) Register(email, pwd, fromUserId string) (bool, string) {
// 用户是否已存在
if userService.IsExistsUser(email) {
return false, email + " 已被注册"
return false, "userHasBeenRegistered-" + email
}
passwd := GenPwd(pwd)
if passwd == "" {
return false, "GenerateHash error"
}
user := info.User{UserId: bson.NewObjectId(), Email: email, Username: email, Pwd: passwd}
if fromUserId != "" && IsObjectId(fromUserId) {
user.FromUserId = bson.ObjectIdHex(fromUserId)
}
user := info.User{UserId: bson.NewObjectId(), Email: email, Username: email, Pwd: Md5(pwd)}
return this.register(user)
}
func (this *AuthService) register(user info.User) (bool, string) {
if userService.AddUser(user) {
// 添加笔记本, 生活, 学习, 工作
userId := user.UserId.Hex()
notebook := info.Notebook{
Seq: -1,
Seq: -1,
UserId: user.UserId}
title2Id := map[string]bson.ObjectId{"life": bson.NewObjectId(), "study": bson.NewObjectId(), "work": bson.NewObjectId()}
for title, objectId := range title2Id {
notebook.Title = title
notebook.NotebookId = objectId
notebook.UserId = user.UserId
notebookService.AddNotebook(notebook);
notebookService.AddNotebook(notebook)
}
email := user.Email
// 添加leanote -> 该用户的共享
leanoteUserId, _ := revel.Config.String("register.sharedUserId"); // "5368c1aa99c37b029d000001";
nk1, _ := revel.Config.String("register.sharedUserShareNotebookId"); // 5368c1aa99c37b029d000002" // leanote
welcomeNoteId, _ := revel.Config.String("register.welcomeNoteId") // "5368c1b919807a6f95000000" // 欢迎来到leanote
if leanoteUserId != "" && nk1 != "" && welcomeNoteId != "" {
shareService.AddShareNotebook(nk1, 0, leanoteUserId, email);
shareService.AddShareNote(welcomeNoteId, 0, leanoteUserId, email);
// 将welcome copy给我
note := noteService.CopySharedNote(welcomeNoteId, title2Id["life"].Hex(), leanoteUserId, user.UserId.Hex());
// 公开为博客
noteUpdate := bson.M{"IsBlog": true}
noteService.UpdateNote(user.UserId.Hex(), user.UserId.Hex(), note.NoteId.Hex(), noteUpdate)
registerSharedUserId := configService.GetGlobalStringConfig("registerSharedUserId")
if registerSharedUserId != "" {
registerSharedNotebooks := configService.GetGlobalArrMapConfig("registerSharedNotebooks")
registerSharedNotes := configService.GetGlobalArrMapConfig("registerSharedNotes")
registerCopyNoteIds := configService.GetGlobalArrayConfig("registerCopyNoteIds")
// 添加共享笔记本
for _, notebook := range registerSharedNotebooks {
perm, _ := strconv.Atoi(notebook["perm"])
shareService.AddShareNotebookToUserId(notebook["notebookId"], perm, registerSharedUserId, userId)
}
// 添加共享笔记
for _, note := range registerSharedNotes {
perm, _ := strconv.Atoi(note["perm"])
shareService.AddShareNoteToUserId(note["noteId"], perm, registerSharedUserId, userId)
}
// 复制笔记
for _, noteId := range registerCopyNoteIds {
note := noteService.CopySharedNote(noteId, title2Id["life"].Hex(), registerSharedUserId, user.UserId.Hex())
// Log(noteId)
// Log("Copy")
// LogJ(note)
noteUpdate := bson.M{"IsBlog": false} // 不要是博客
noteService.UpdateNote(user.UserId.Hex(), note.NoteId.Hex(), noteUpdate, -1)
}
}
//---------------
// 添加一条userBlog
blogService.UpdateUserBlog(info.UserBlog{UserId: user.UserId,
Title: user.Username + " 's Blog",
SubTitle: "love leanote!",
AboutMe: "Hello, I am (^_^)",
})
blogService.UpdateUserBlog(info.UserBlog{UserId: user.UserId,
Title: user.Username + " 's Blog",
SubTitle: "Love Leanote!",
AboutMe: "Hello, I am (^_^)",
CanComment: true,
})
// 添加一个单页面
blogService.AddOrUpdateSingle(user.UserId.Hex(), "", "About Me", "Hello, I am (^_^)")
}
return true, ""
}
@@ -91,7 +123,7 @@ func (this *AuthService) register(user info.User) (bool, string) {
func (this *AuthService) getUsername(thirdType, thirdUsername string) (username string) {
username = thirdType + "-" + thirdUsername
i := 1
for ;; {
for {
if !userService.IsExistsUserByUsername(username) {
return
}
@@ -107,11 +139,11 @@ func (this *AuthService) ThirdRegister(thirdType, thirdUserId, thirdUsername str
}
username := this.getUsername(thirdType, thirdUsername)
userInfo = info.User{UserId: bson.NewObjectId(),
Username: username,
ThirdUserId: thirdUserId,
userInfo = info.User{UserId: bson.NewObjectId(),
Username: username,
ThirdUserId: thirdUserId,
ThirdUsername: thirdUsername,
}
}
_, _ = this.register(userInfo)
return
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,610 @@
package service
import (
"fmt"
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
// 配置服务
// 只是全局的, 用户的配置没有
type ConfigService struct {
adminUserId string
siteUrl string
adminUsername string
// 全局的
GlobalAllConfigs map[string]interface{}
GlobalStringConfigs map[string]string
GlobalArrayConfigs map[string][]string
GlobalMapConfigs map[string]map[string]string
GlobalArrMapConfigs map[string][]map[string]string
}
// appStart时 将全局的配置从数据库中得到作为全局
func (this *ConfigService) InitGlobalConfigs() bool {
this.GlobalAllConfigs = map[string]interface{}{}
this.GlobalStringConfigs = map[string]string{}
this.GlobalArrayConfigs = map[string][]string{}
this.GlobalMapConfigs = map[string]map[string]string{}
this.GlobalArrMapConfigs = map[string][]map[string]string{}
this.adminUsername, _ = revel.Config.String("adminUsername")
if this.adminUsername == "" {
this.adminUsername = "admin"
}
this.siteUrl, _ = revel.Config.String("site.url")
userInfo := userService.GetUserInfoByAny(this.adminUsername)
if userInfo.UserId == "" {
return false
}
this.adminUserId = userInfo.UserId.Hex()
configs := []info.Config{}
// db.ListByQ(db.Configs, bson.M{"UserId": userInfo.UserId}, &configs)
db.ListByQ(db.Configs, bson.M{}, &configs)
for _, config := range configs {
if config.IsArr {
this.GlobalArrayConfigs[config.Key] = config.ValueArr
this.GlobalAllConfigs[config.Key] = config.ValueArr
} else if config.IsMap {
this.GlobalMapConfigs[config.Key] = config.ValueMap
this.GlobalAllConfigs[config.Key] = config.ValueMap
} else if config.IsArrMap {
this.GlobalArrMapConfigs[config.Key] = config.ValueArrMap
this.GlobalAllConfigs[config.Key] = config.ValueArrMap
} else {
this.GlobalStringConfigs[config.Key] = config.ValueStr
this.GlobalAllConfigs[config.Key] = config.ValueStr
}
}
// site URL
if s, ok := this.GlobalStringConfigs["siteUrl"]; !ok || s != "" {
this.GlobalStringConfigs["siteUrl"] = this.siteUrl
}
return true
}
func (this *ConfigService) GetSiteUrl() string {
s := this.GetGlobalStringConfig("siteUrl")
if s != "" {
return s
}
return this.siteUrl
}
func (this *ConfigService) GetAdminUsername() string {
return this.adminUsername
}
func (this *ConfigService) GetAdminUserId() string {
return this.adminUserId
}
// 通用方法
func (this *ConfigService) updateGlobalConfig(userId, key string, value interface{}, isArr, isMap, isArrMap bool) bool {
// 判断是否存在
if _, ok := this.GlobalAllConfigs[key]; !ok {
// 需要添加
config := info.Config{ConfigId: bson.NewObjectId(),
UserId: bson.ObjectIdHex(userId), // 没用
Key: key,
IsArr: isArr,
IsMap: isMap,
IsArrMap: isArrMap,
UpdatedTime: time.Now(),
}
if isArr {
v, _ := value.([]string)
config.ValueArr = v
this.GlobalArrayConfigs[key] = v
} else if isMap {
v, _ := value.(map[string]string)
config.ValueMap = v
this.GlobalMapConfigs[key] = v
} else if isArrMap {
v, _ := value.([]map[string]string)
config.ValueArrMap = v
this.GlobalArrMapConfigs[key] = v
} else {
v, _ := value.(string)
config.ValueStr = v
this.GlobalStringConfigs[key] = v
}
return db.Insert(db.Configs, config)
} else {
i := bson.M{"UpdatedTime": time.Now()}
this.GlobalAllConfigs[key] = value
if isArr {
v, _ := value.([]string)
i["ValueArr"] = v
this.GlobalArrayConfigs[key] = v
} else if isMap {
v, _ := value.(map[string]string)
i["ValueMap"] = v
this.GlobalMapConfigs[key] = v
} else if isArrMap {
v, _ := value.([]map[string]string)
i["ValueArrMap"] = v
this.GlobalArrMapConfigs[key] = v
} else {
v, _ := value.(string)
i["ValueStr"] = v
this.GlobalStringConfigs[key] = v
}
// return db.UpdateByQMap(db.Configs, bson.M{"UserId": bson.ObjectIdHex(userId), "Key": key}, i)
return db.UpdateByQMap(db.Configs, bson.M{"Key": key}, i)
}
}
// 更新用户配置
func (this *ConfigService) UpdateGlobalStringConfig(userId, key string, value string) bool {
return this.updateGlobalConfig(userId, key, value, false, false, false)
}
func (this *ConfigService) UpdateGlobalArrayConfig(userId, key string, value []string) bool {
return this.updateGlobalConfig(userId, key, value, true, false, false)
}
func (this *ConfigService) UpdateGlobalMapConfig(userId, key string, value map[string]string) bool {
return this.updateGlobalConfig(userId, key, value, false, true, false)
}
func (this *ConfigService) UpdateGlobalArrMapConfig(userId, key string, value []map[string]string) bool {
return this.updateGlobalConfig(userId, key, value, false, false, true)
}
// 获取全局配置, 博客平台使用
func (this *ConfigService) GetGlobalStringConfig(key string) string {
return this.GlobalStringConfigs[key]
}
func (this *ConfigService) GetGlobalArrayConfig(key string) []string {
arr := this.GlobalArrayConfigs[key]
if arr == nil {
return []string{}
}
return arr
}
func (this *ConfigService) GetGlobalMapConfig(key string) map[string]string {
m := this.GlobalMapConfigs[key]
if m == nil {
return map[string]string{}
}
return m
}
func (this *ConfigService) GetGlobalArrMapConfig(key string) []map[string]string {
m := this.GlobalArrMapConfigs[key]
if m == nil {
return []map[string]string{}
}
return m
}
func (this *ConfigService) IsOpenRegister() bool {
return this.GetGlobalStringConfig("openRegister") != ""
}
//-------
// 修改共享笔记的配置
func (this *ConfigService) UpdateShareNoteConfig(registerSharedUserId string,
registerSharedNotebookPerms, registerSharedNotePerms []int,
registerSharedNotebookIds, registerSharedNoteIds, registerCopyNoteIds []string) (ok bool, msg string) {
defer func() {
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err)
}
}()
// 用户是否存在?
if registerSharedUserId == "" {
ok = true
msg = "share userId is blank, So it share nothing to register"
this.UpdateGlobalStringConfig(this.adminUserId, "registerSharedUserId", "")
return
} else {
user := userService.GetUserInfo(registerSharedUserId)
if user.UserId == "" {
ok = false
msg = "no such user: " + registerSharedUserId
return
} else {
this.UpdateGlobalStringConfig(this.adminUserId, "registerSharedUserId", registerSharedUserId)
}
}
notebooks := []map[string]string{}
// 共享笔记本
if len(registerSharedNotebookIds) > 0 {
for i := 0; i < len(registerSharedNotebookIds); i++ {
// 判断笔记本是否存在
notebookId := registerSharedNotebookIds[i]
if notebookId == "" {
continue
}
notebook := notebookService.GetNotebook(notebookId, registerSharedUserId)
if notebook.NotebookId == "" {
ok = false
msg = "The user has no such notebook: " + notebookId
return
} else {
perm := "0"
if registerSharedNotebookPerms[i] == 1 {
perm = "1"
}
notebooks = append(notebooks, map[string]string{"notebookId": notebookId, "perm": perm})
}
}
}
this.UpdateGlobalArrMapConfig(this.adminUserId, "registerSharedNotebooks", notebooks)
notes := []map[string]string{}
// 共享笔记
if len(registerSharedNoteIds) > 0 {
for i := 0; i < len(registerSharedNoteIds); i++ {
// 判断笔记本是否存在
noteId := registerSharedNoteIds[i]
if noteId == "" {
continue
}
note := noteService.GetNote(noteId, registerSharedUserId)
if note.NoteId == "" {
ok = false
msg = "The user has no such note: " + noteId
return
} else {
perm := "0"
if registerSharedNotePerms[i] == 1 {
perm = "1"
}
notes = append(notes, map[string]string{"noteId": noteId, "perm": perm})
}
}
}
this.UpdateGlobalArrMapConfig(this.adminUserId, "registerSharedNotes", notes)
// 复制
noteIds := []string{}
if len(registerCopyNoteIds) > 0 {
for i := 0; i < len(registerCopyNoteIds); i++ {
// 判断笔记本是否存在
noteId := registerCopyNoteIds[i]
if noteId == "" {
continue
}
note := noteService.GetNote(noteId, registerSharedUserId)
if note.NoteId == "" {
ok = false
msg = "The user has no such note: " + noteId
return
} else {
noteIds = append(noteIds, noteId)
}
}
}
this.UpdateGlobalArrayConfig(this.adminUserId, "registerCopyNoteIds", noteIds)
ok = true
return
}
// 添加备份
func (this *ConfigService) AddBackup(path, remark string) bool {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
n := time.Now().Unix()
nstr := fmt.Sprintf("%v", n)
backups = append(backups, map[string]string{"createdTime": nstr, "path": path, "remark": remark})
return this.UpdateGlobalArrMapConfig(this.adminUserId, "backups", backups)
}
func (this *ConfigService) getBackupDirname() string {
n := time.Now()
y, m, d := n.Date()
return strconv.Itoa(y) + "_" + m.String() + "_" + strconv.Itoa(d) + "_" + fmt.Sprintf("%v", n.Unix())
}
func (this *ConfigService) Backup(remark string) (ok bool, msg string) {
binPath := configService.GetGlobalStringConfig("mongodumpPath")
config := revel.Config
dbname, _ := config.String("db.dbname")
host, _ := revel.Config.String("db.host")
port, _ := revel.Config.String("db.port")
username, _ := revel.Config.String("db.username")
password, _ := revel.Config.String("db.password")
// mongodump -h localhost -d leanote -o /root/mongodb_backup/leanote-9-22/ -u leanote -p nKFAkxKnWkEQy8Vv2LlM
binPath = binPath + " -h " + host + " -d " + dbname + " --port " + port
if username != "" {
binPath += " -u " + username + " -p " + password
}
// 保存的路径
dir := revel.BasePath + "/mongodb_backup/" + this.getBackupDirname()
binPath += " -o " + dir
err := os.MkdirAll(dir, 0755)
if err != nil {
ok = false
msg = fmt.Sprintf("%v", err)
return
}
cmd := exec.Command("/bin/sh", "-c", binPath)
Log(binPath)
b, err := cmd.Output()
if err != nil {
msg = fmt.Sprintf("%v", err)
ok = false
Log("error:......")
Log(string(b))
return
}
ok = configService.AddBackup(dir, remark)
return ok, msg
}
// 还原
func (this *ConfigService) Restore(createdTime string) (ok bool, msg string) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break
}
}
if i == len(backups) {
return false, "Backup Not Found"
}
// 先备份当前
ok, msg = this.Backup("Auto backup when restore from " + backup["createdTime"])
if !ok {
return
}
// mongorestore -h localhost -d leanote --directoryperdb /home/user1/gopackage/src/github.com/leanote/leanote/mongodb_backup/leanote_install_data/
binPath := configService.GetGlobalStringConfig("mongorestorePath")
config := revel.Config
dbname, _ := config.String("db.dbname")
host, _ := revel.Config.String("db.host")
port, _ := revel.Config.String("db.port")
username, _ := revel.Config.String("db.username")
password, _ := revel.Config.String("db.password")
// mongorestore -h localhost -d leanote -o /root/mongodb_backup/leanote-9-22/ -u leanote -p nKFAkxKnWkEQy8Vv2LlM
binPath = binPath + " --drop -h " + host + " -d " + dbname + " --port " + port
if username != "" {
binPath += " -u " + username + " -p " + password
}
path := backup["path"] + "/" + dbname
// 判断路径是否存在
if !IsDirExists(path) {
return false, path + " Is Not Exists"
}
binPath += " " + path
cmd := exec.Command("/bin/sh", "-c", binPath)
Log(binPath)
b, err := cmd.Output()
if err != nil {
msg = fmt.Sprintf("%v", err)
ok = false
Log("error:......")
Log(string(b))
return
}
return true, ""
}
func (this *ConfigService) DeleteBackup(createdTime string) (bool, string) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break
}
}
if i == len(backups) {
return false, "Backup Not Found"
}
// 删除文件夹之
err := os.RemoveAll(backups[i]["path"])
if err != nil {
return false, fmt.Sprintf("%v", err)
}
// 删除之
backups = append(backups[0:i], backups[i+1:]...)
ok := this.UpdateGlobalArrMapConfig(this.adminUserId, "backups", backups)
return ok, ""
}
func (this *ConfigService) UpdateBackupRemark(createdTime, remark string) (bool, string) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break
}
}
if i == len(backups) {
return false, "Backup Not Found"
}
backup["remark"] = remark
ok := this.UpdateGlobalArrMapConfig(this.adminUserId, "backups", backups)
return ok, ""
}
// 得到备份
func (this *ConfigService) GetBackup(createdTime string) (map[string]string, bool) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break
}
}
if i == len(backups) {
return map[string]string{}, false
}
return backup, true
}
//--------------
// sub domain
var defaultDomain string
var schema = "http://"
var port string
func init() {
revel.OnAppStart(func() {
/*
不用配置的, 因为最终通过命令可以改, 而且有的使用nginx代理
port = strconv.Itoa(revel.HttpPort)
if port != "80" {
port = ":" + port
} else {
port = "";
}
*/
siteUrl, _ := revel.Config.String("site.url") // 已包含:9000, http, 去掉成 leanote.com
if strings.HasPrefix(siteUrl, "http://") {
defaultDomain = siteUrl[len("http://"):]
} else if strings.HasPrefix(siteUrl, "https://") {
defaultDomain = siteUrl[len("https://"):]
schema = "https://"
}
// port localhost:9000
ports := strings.Split(defaultDomain, ":")
if len(ports) == 2 {
port = ports[1]
}
if port == "80" {
port = ""
} else {
port = ":" + port
}
})
}
func (this *ConfigService) GetSchema() string {
return schema
}
// 默认
func (this *ConfigService) GetDefaultDomain() string {
return defaultDomain
}
// note
func (this *ConfigService) GetNoteDomain() string {
return "/note"
}
func (this *ConfigService) GetNoteUrl() string {
return this.GetNoteDomain()
}
// blog
func (this *ConfigService) GetBlogDomain() string {
return "/blog"
}
func (this *ConfigService) GetBlogUrl() string {
return this.GetBlogDomain()
}
// lea
func (this *ConfigService) GetLeaDomain() string {
return "/lea"
}
func (this *ConfigService) GetLeaUrl() string {
return schema + this.GetLeaDomain()
}
func (this *ConfigService) GetUserUrl(domain string) string {
return schema + domain + port
}
func (this *ConfigService) GetUserSubUrl(subDomain string) string {
return schema + subDomain + "." + this.GetDefaultDomain()
}
// 是否允许自定义域名
func (this *ConfigService) AllowCustomDomain() bool {
return configService.GetGlobalStringConfig("allowCustomDomain") != ""
}
// 是否是好的自定义域名
func (this *ConfigService) IsGoodCustomDomain(domain string) bool {
blacks := this.GetGlobalArrayConfig("blackCustomDomains")
for _, black := range blacks {
if strings.Contains(domain, black) {
return false
}
}
return true
}
func (this *ConfigService) IsGoodSubDomain(domain string) bool {
blacks := this.GetGlobalArrayConfig("blackSubDomains")
LogJ(blacks)
for _, black := range blacks {
if domain == black {
return false
}
}
return true
}
// 上传大小
func (this *ConfigService) GetUploadSize(key string) float64 {
f, _ := strconv.ParseFloat(this.GetGlobalStringConfig(key), 64)
return f
}
func (this *ConfigService) GetInt64(key string) int64 {
f, _ := strconv.ParseInt(this.GetGlobalStringConfig(key), 10, 64)
return f
}
func (this *ConfigService) GetInt32(key string) int32 {
f, _ := strconv.ParseInt(this.GetGlobalStringConfig(key), 10, 32)
return int32(f)
}
func (this *ConfigService) GetUploadSizeLimit() map[string]float64 {
return map[string]float64{
"uploadImageSize": this.GetUploadSize("uploadImageSize"),
"uploadBlogLogoSize": this.GetUploadSize("uploadBlogLogoSize"),
"uploadAttachSize": this.GetUploadSize("uploadAttachSize"),
"uploadAvatarSize": this.GetUploadSize("uploadAvatarSize"),
}
}
// 为用户得到全局的配置
// NoteController调用
func (this *ConfigService) GetGlobalConfigForUser() map[string]interface{} {
uploadSizeConfigs := this.GetUploadSizeLimit()
config := map[string]interface{}{}
for k, v := range uploadSizeConfigs {
config[k] = v
}
return config
}
// 主页是否是管理员的博客页
func (this *ConfigService) HomePageIsAdminsBlog() bool {
return this.GetGlobalStringConfig("homePage") == ""
}
func (this *ConfigService) GetVersion() string {
return "2.4"
}

Some files were not shown because too many files have changed in this diff Show More