Compare commits

..
592 Commits
Author SHA1 Message Date
lealife 290772b437 init index 2016-05-30 11:51:41 +08: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
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
life 3ffb10b923 mongodb initial data 2014-09-04 16:15:57 +08:00
life ba9b35c46e logo changed 2014-09-04 14:45:12 +08:00
life acc67754c3 logo changed 2014-09-04 14:23:01 +08:00
life 1ed9f0c96d favicon changed 2014-09-04 14:15:38 +08:00
life 2db2a55e81 favicon changed 2014-09-04 13:50:12 +08:00
life 634b17c6f7 leanote logo font 2014-09-04 13:27:36 +08:00
life 468ad4dec7 leanote logo font 2014-09-04 12:53:04 +08:00
life 7cbd38e8bf mgo package changed to 'gopkg.in/mgo.v2/bson' 2014-09-02 15:45:44 +08:00
life 1d8be4c012 golang template 2014-08-28 11:53:04 +08:00
life 7b6a5c0929 leaui image bug fix 2014-07-15 20:59:43 +08:00
life fe10dbbd77 leaui image fix bugs 2014-07-15 20:55:28 +08:00
life 7db9d95108 leaui image 2014-07-14 22:59:00 +08:00
life 91eb893814 m 2014-07-14 22:58:09 +08:00
life fcc86de37c leaui image 2014-07-08 22:09:48 +08:00
life 61a0b9caa0 leaui image 2014-07-08 22:05:33 +08:00
life 95fe0c5289 leaui image 2014-07-08 21:45:43 +08:00
life bb0350fdac update leaui_image 2014-06-28 23:12:34 +08:00
life 1494b25129 add leaui_image plugin for replace leanote_image. on a train to ChangSha 2014-06-28 23:07:34 +08:00
life 06eb27faab loading css 2014-06-28 12:24:29 +08:00
life 121eab5d38 thumb css 2014-06-28 12:18:15 +08:00
life 37da418bdd thumb css 2014-06-28 12:17:09 +08:00
life eea6ad07f8 thumb css 2014-06-28 12:15:54 +08:00
life b0402f29dd markdown 2014-06-24 21:41:02 +08:00
life 8e05e433de blog 2014-06-24 21:14:17 +08:00
life a0acd31fcf tinyce table 2014-06-17 19:46:34 +08:00
life a5b525affd update readme 2014-06-15 13:31:08 +08:00
life 414639dc3d fix notebook 2014-06-14 17:44:04 +08:00
life cf4accc110 slim scroll 2014-06-14 17:29:29 +08:00
life 8d4a757ee9 update readme 2014-06-12 15:12:08 +08:00
life 68bacdc3bf update readme 2014-06-12 15:11:12 +08:00
life 3df6fa2bb7 update readme 2014-06-12 11:35:14 +08:00
life e8c0001721 update readme 2014-06-12 11:34:27 +08:00
life 4210d56a0d update readme 2014-06-12 11:31:37 +08:00
life 63b65920db paste image on chrome 2014-06-08 18:03:23 +08:00
life c55f364153 paste image on chrome 2014-06-08 17:57:19 +08:00
life 8af64c7b74 toggle writing mode bookmark 2014-06-05 19:04:39 +08:00
life 7bb05bd9e7 index 2014-05-27 13:55:18 +08:00
life abf3297639 release 0.3 2014-05-27 13:33:05 +08:00
life ad00f36fd9 release 0.3 2014-05-27 13:32:30 +08:00
life c549957cdc release 0.3 2014-05-27 13:30:07 +08:00
life 7f9356c5a7 release 0.3 2014-05-27 13:29:19 +08:00
life 910b079c55 resize editor 2014-05-27 12:59:14 +08:00
life b1b36cec23 update email 2014-05-27 11:37:03 +08:00
life 8eab8c7310 fix updateEmail 2014-05-27 11:21:35 +08:00
life ddd0c13d34 editor fix 2014-05-21 10:08:47 +08:00
life 2d107b754d writting mode resize editor 2014-05-15 22:55:16 +08:00
life 872f4e68cf writting mode resize editor 2014-05-15 22:41:29 +08:00
life 8f86baa616 writting mode resize editor 2014-05-15 22:36:32 +08:00
life 1fa03e901d angularjs mobile v1.0 2014-05-14 22:46:57 +08:00
life 04d41dff4a angularjs mobile v1.0 2014-05-14 22:41:39 +08:00
life 4d1f3b957e angularjs mobile v1.0 2014-05-14 22:35:56 +08:00
life 0929c715c0 angularjs mobile v1.0 2014-05-14 22:30:28 +08:00
life cac06e673a Merge branch 'develop' 2014-05-14 22:25:34 +08:00
life ab5e5c7b76 angularjs mobile v0.1 2014-05-14 22:23:35 +08:00
life 88ad35e9be test 2014-05-13 21:22:45 +08:00
life cd61592f74 Merge branch 'master' into develop 2014-05-13 21:19:29 +08:00
life bf6590eef9 lock page 2014-05-13 21:18:33 +08:00
life 0a75902865 i18n 2014-05-13 17:44:31 +08:00
life 58d1e0ec00 i18n 2014-05-13 17:42:55 +08:00
life 5244c89987 writing mode fix 2014-05-13 17:17:13 +08:00
life 6ed0e30d2b writing mode fix 2014-05-13 17:13:02 +08:00
life 2f79331ccf writing mode leanote nav 2014-05-13 17:04:14 +08:00
life 76ecc00ad0 fix bug 2014-05-13 16:54:41 +08:00
life 418c70bad2 editor tabIndex 2014-05-13 16:47:06 +08:00
life 077e702ba1 writing mode v1.0 2014-05-13 15:29:23 +08:00
life 6ee8fc999d writing mode v1.0 2014-05-13 15:23:27 +08:00
life 4f7d2b5645 default theme 2014-05-13 15:18:21 +08:00
life 7a89eca49d writing mode v1.0 2014-05-13 15:16:49 +08:00
life d392c0eb36 merge 2014-05-13 15:15:12 +08:00
life e3f4b50c53 writing mode v1.0 2014-05-13 15:12:38 +08:00
life 03f2ebe3f7 writting mode v0.1 2014-05-13 14:34:30 +08:00
life 48c2474668 for brunch test 2014-05-12 14:10:13 +08:00
life cbe62557f8 for develop writting mod 2014-05-12 14:03:19 +08:00
life 0b9dd488c2 update readme.md 2014-05-11 22:46:32 +08:00
life 3db06ed738 update readme.md 2014-05-11 22:46:00 +08:00
life 76b279c1e5 update readme.md 2014-05-11 22:44:14 +08:00
life 6eb4702cd2 mobile 2014-05-10 17:35:20 +08:00
life f0b4012118 fix markdown editor 2014-05-10 16:50:03 +08:00
life 0e45f2c17f editor i18n 2014-05-10 16:28:09 +08:00
life 3baa31d44c note i18n 2014-05-10 16:12:38 +08:00
life 365ad3853c blog i18n 2014-05-10 16:01:50 +08:00
life 98cf275e37 update readme.md 2014-05-10 14:27:50 +08:00
leanote 498f2dc3a8 Update README.md 2014-05-09 21:20:48 +08:00
leanote 98b048235c Update README.md 2014-05-09 21:18:56 +08:00
leanote 273028ea96 Update README.md 2014-05-09 20:56:11 +08:00
leanote a93048b016 Merge pull request #3 from jaseg/master
README: Small grammar, style & markdown fixes
2014-05-09 20:39:34 +08:00
jaseg b63e79954a README: Grammar & markdown fixes 2014-05-09 14:27:03 +02:00
life b8b103ace1 update readme.md 2014-05-09 18:14:01 +08:00
life b2436ee711 update readme.md 2014-05-09 18:08:11 +08:00
life 91f4698c09 update readme.md 2014-05-09 18:06:58 +08:00
life 2605b86e00 update readme.md 2014-05-09 18:03:58 +08:00
life cd7915f92b deleve bin files 2014-05-09 18:01:23 +08:00
life 6f413bfa1f remove 2014-05-09 17:58:46 +08:00
life 298f7bc1d6 release v0.1 2014-05-09 17:56:14 +08:00
2768 changed files with 259452 additions and 135023 deletions

No files matched your search

+7 -2
View File
@@ -2,13 +2,18 @@
pkg pkg
bin2 bin2
bin/i18n bin/i18n
bin/leanote*
bin/release bin/release
bin/test.sh
bin/tmp bin/tmp
bin/test bin/test
conf/app.conf bin/src
conf/routes
public/upload public/upload
app/routes/routes.go app/routes/routes.go
app/tmp/main.go app/tmp/main.go
.DS_Store .DS_Store
.settings .settings
.project
public/config.codekit
files
/node_modules
+37
View File
@@ -0,0 +1,37 @@
language: go
go: 1.3
services:
- mongodb # 2.4.12
install:
- export PATH=$PATH:$HOME/gopath/bin
- go get -v github.com/leanote/leanote/app
- 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/
- go test github.com/leanote/leanote/app/tests
# 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
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-min.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,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']);
+21 -1
View File
@@ -1,4 +1,24 @@
GNU GENERAL PUBLIC LICENSE 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 Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+125 -72
View File
@@ -1,112 +1,165 @@
## Introduction # Leanote
Leanote, a cloud note. You can create your own cloud note by 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)
## Features ## 1. Introduction
* Knowledge: manage your knowledge in leanote. leanote contains tinymce editor and markdown editor, just enjoy yourself in writting.
* Share: share your knowledge to your friends in leanote. Well, you are not alone, you can invite your friends to join your cloud note and share your knowledge each other.
* Cooperation: collaborate with friends to improve your knowledge.
* Blog: public your knowledge and leanote be your blog.
## Why we create leanote Leanote, Not Just A Notepad!
To be honest, our inspiration comes from evernote, and we use evenote to manage our knowledge everyday. But we find that: ![leanote.png](leanote.png "")
* Evernote's editor can't meet our needs, it hasn't document navigation, can't put our codes(as a programmer, put codes is the basic needs), can't resize images...)
* We like markdown, but evernote don't support it. **Some Features**
* We want to public our knowledge, so we have our blog(such as wordpress) and evernote, but why can't be the one!
* 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.
**Other Features**
* Support Markdown
* Writting mode
* Export note to PDF
* Note batch operation
* Custom theme for 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!
* ...... * ......
## How to use it ## 3. How to install Leanote
Leanote build with golang(revel) and mongodb. so you must install mongodb at first.
### Install mongodb Leanote 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). And all the products are open source!
For more tips please go https://github.com/leanote/leanote/wiki/mongodb-in-leanote
Go http://www.mongodb.org to download and install it. You can install Leanote on your server, and use Leanote App (Desktop, iOs, Android) to sync notes with your self-hosted server.
### Export initial mongodb data Welcome to sign up on https://leanote.com, Leanote team provide a stable and reliable service for you.
The mongodb data is in path_to_leante/mongodb_backup/leanote_install_data More information about how to install Leanote please see:
``` * [Leanote binary installation tutorial](https://github.com/leanote/leanote/wiki/leanote-binary-distribution-installation-tutorial)
$> mongorestore -h localhost -d leanote --directoryperdb path_to_leante/mongodb_backup/leanote_install_data * [Leanote source installation tutorial](https://github.com/leanote/leanote/wiki/leanote-source-installation-tutorial)
```
The initial data contains two users: ## 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)
user1 username: leanote, password: abc123
user2 username: admin, password: abc123
```
### Configuration ## 5. Docs
Copy path_to_leante/conf/app-default.conf to path_to_leante/conf/app.conf, the options contains: Please see [wiki](https://github.com/leanote/leanote/wiki).
``mongodb`` **required** ## 6. Contributors
Thank you to all the [contributors](https://github.com/leanote/leanote/graphs/contributors) on
this project. Your help is much appreciated.
```Shell ## 7.Join us
db.host=localhost
db.port=27017
db.dbname=leanote
db.username=
db.password=
```
``http.port`` Please fork this repository and contribute back using [pull requests](https://github.com/leanote/leanote/pulls).
Default is 80 If you find some problems or has some good ideas, please submit [issues](https://github.com/leanote/leanote/issues).
``site.url`` You are always welcomed!
Default is http://localhost, you must config it when your domain isn't it, it is used when upload images. ## 8. Donation
Support us, [donate us](http://leanote.org/#donate). And thanks [donators](http://leanote.leanote.com/post/leanote-donation-list).
``email`` ## 9. Related projects
for find password * [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
``adminUsername`` You are welcome to join us.
Default is admin. The index site is the adminUsername's blog ## 9. Support & Join us
For more infomation please see app/app.conf and revel manuals http://revel.github.io * Email: leanote@leanote.com
* [Leanote BBS](http://bbs.leanote.com)
* [Leanote Google Group](https://groups.google.com/forum/#!forum/leanote)
* QQ Group: 326073529, 256076853, 158716820
### Run leanote -----------------------------------------------------------------------
``` # Leanote
$> cd path_to_leanote/bin
$> sudo sh run.sh
```
## How to develop leanote ## 1. 介绍
For more tips please go https://github.com/leanote/leanote/wiki/How-to-develop-leanote Leanote, 不只是笔记!
Leanote is a app based on revel(http://revel.github.io), so if you want to develop leanote as you want, you must be familar with revel. **特性**
### Install golang * 知识管理: 通过Leanote来管理知识, Leanote有易操作的界面, 包含两款编辑器富文本编辑器和Markdown编辑器. 在Leanote, 你可以尽情享受写作.
* 博客: Leanote也可以作为你的博客, 将知识公开成博客, 让Leanote把你的知识传播的更远!
* 分享: 你也可以通过分享知识给好友, 让好友拥有你的知识.
* 协作: 在分享的同时也可以与好友一起协作知识.
Install golang and set GOPATH **一些其它特性**
### Install revel * 支持Markdown编辑
``` * 写作模式
go get github.com/revel/revel * 支持PDF导出
go get github.com/revel/cmd/revel * 支持批量操作
``` * 博客自定义主题, 实现高度定制化
### Get leanote ## 2. 为什么我们要创建Leanote?
说实话, 我们曾是evernote的忠实粉丝, 但是我们也发现evernote的不足:
* evernote的编辑器不能满足我们的需求, 不能贴代码(格式会乱掉, 作为程序员, 代码是我们的基本需求啊), 图片不能缩放.
* 我们是markdown的爱好者, 可是evernote竟然没有.
* 我们也想将知识公开, 所以我们有自己的博客, 如wordpress, 但为什么这两者不能合二为一呢?
* 还有...
``` ## 3.安装Leanote
go get github.com/leanote/leanote/app
```
### Build/Run leanote via revel Leanote云笔记产品包括: Leanote Web & Server(即本仓库), 桌面客户端, ios, android. 4端全部开源! 你可以下载它安装在自己的服务器上, Leanote的客户端可以连接自建的服务.
cp conf/routes-default to conf/routes 欢迎在 https://leanote.com 上注册, Leanote团队为你提供稳定可靠的服务.
Now you can modify leanote source and build/run with revel 这里详细整理了Leanote二进版和Leanote开发版的安装教程, 请移步至:
``` * [Leanote二进制详细安装教程](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)
revel run github.com/leanote/leanote * [Leanote源码详细安装教程](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)
```
## 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 相关文档
* [Leanote二进制版详细安装教程](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源码详细安装教程](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 source Leanote源码导读](https://github.com/leanote/leanote/wiki/Leanote-source-leanote源码导读)
* [Leanote blog theme api(中文版)](https://github.com/leanote/leanote/wiki/leanote-blog-theme-api)
* [How to develop leanote 如何开发Leanote](https://github.com/leanote/leanote/wiki/How-to-develop-leanote-如何开发leanote)
更多文档请查看 [wiki](https://github.com/leanote/leanote/wiki).
## 6. 贡献者
谢谢 [贡献者](https://github.com/leanote/leanote/graphs/contributors) 的贡献, Leanote因有你们而更完美!
## 7. 加入我们
欢迎提交[pull requests](https://github.com/leanote/leanote/pulls) 到Leanote.
有任何问题或建议, 欢迎提交[issue](https://github.com/leanote/leanote/issues).
Leanote还有很多问题, 如果你喜欢它, 欢迎加入我们一起完善Leanote.
## 8. 捐赠
支持我们, [捐赠Leanote](http://leanote.org/#donate). 感谢[捐赠者](http://leanote.leanote.com/post/leanote-donation-list), 谢谢你们的鼓励, Leanote会一直坚持!
## 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), 开发阶段
欢迎加入我们!
## 联系&加入我们
* Email: leanote@leanote.com
* [Leanote 社区](http://bbs.leanote.com)
* QQ群: 326073529, 256076853, 158716820
* [Leanote Google Group](https://groups.google.com/forum/#!forum/leanote)
Welcome to join with us and contribute your code to leanote! Thanks.
+73
View File
@@ -0,0 +1,73 @@
# Leanote
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/leanote/leanote?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build Status](https://travis-ci.org/leanote/leanote.svg)](https://travis-ci.org/leanote/leanote)
## 1. 介绍
Leanote, 不只是笔记!
![leanote.png](leanote.png "")
**特性**
* 知识管理: 通过leanote来管理知识, leanote有易操作的界面, 包含两款编辑器tinymce和markdown. 在leanote, 你可以尽情享受写作.
* 分享: 你也可以通过分享知识给好友, 让好友拥有你的知识.
* 协作: 在分享的同时也可以与好友一起协作知识.
* 博客: leanote也可以作为你的博客, 将知识公开成博客, 让leanote把你的知识传播的更远!
## 2. 为什么我们要创建leanote?
说实话, 我们曾是evernote的忠实粉丝, 但是我们也发现evernote的不足:
* evernote的编辑器不能满足我们的需求, 不能贴代码(格式会乱掉, 作为程序员, 代码是我们的基本需求啊), 图片不能缩放.
* 我们是markdown的爱好者, 可是evernote竟然没有.
* 我们也想将知识公开, 所以我们有自己的博客, 如wordpress, 但为什么这两者不能合二为一呢?
* 还有...
## 3.安装leanote
leanote是一款私有云笔记, 你可以下载它安装在自己的服务器上, 当然也可以在 http://leanote.com 上注册.
这里详细整理了leanote二进版和leanote开发版的安装教程, 请移步至:
* [leanote二进制详细安装教程](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开发版详细安装教程](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)
## 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 相关文档
* [leanote二进制版详细安装教程](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开发版详细安装教程](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 source leanote源码导读](https://github.com/leanote/leanote/wiki/Leanote-source-leanote源码导读)
* [leanote blog theme api(中文版)](https://github.com/leanote/leanote/wiki/leanote-blog-theme-api)
* [How to develop leanote 如何开发leanote](https://github.com/leanote/leanote/wiki/How-to-develop-leanote-如何开发leanote)
更多文档请查看 [wiki](https://github.com/leanote/leanote/wiki).
## 6. 贡献者
多谢 [贡献者](https://github.com/leanote/leanote/graphs/contributors) 的贡献, leanote因有你们而更完美!
## 7. 加入我们
欢迎提交[pull requests](https://github.com/leanote/leanote/pulls) 到leanote.
有任何问题或建议, 欢迎提交[issue](https://github.com/leanote/leanote/issues).
Leanote还有很多问题, 如果你喜欢它, 欢迎加入我们一起完善leanote.
## 8. 捐赠
支持我们, [捐赠Leanote](http://leanote.org/#donate). 感谢[捐赠者](http://leanote.leanote.com/post/leanote-donation-list), 谢谢你们的鼓励, Leanote会一直坚持!
## 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/Dminter/leanote-android-client), 开发阶段
欢迎加入我们!
## 联系&加入我们
* Email: leanote@leanote.com
* [leanote 社区](http://bbs.leanote.com)
* QQ群: 158716820, 256076853
* [Leanote Google Group](https://groups.google.com/forum/#!forum/leanote)
----------------------------------------------------------------
[English](README.md)
+19
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")
}
+52
View File
@@ -0,0 +1,52 @@
package controllers
import (
"github.com/revel/revel"
// "encoding/json"
"github.com/leanote/leanote/app/info"
"gopkg.in/mgo.v2/bson"
// . "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)
}
func (c Album) DeleteAlbum(albumId string) revel.Result {
re, msg := albumService.DeleteAlbum(c.GetUserId(), albumId)
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()}
re := albumService.AddAlbum(album)
if re {
return c.RenderJson(album)
} else {
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))
}
+227
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
}
+88 -39
View File
@@ -1,9 +1,11 @@
package controllers package controllers
import ( import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea" . "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"strings"
// "strconv"
) )
// 用户登录/注销/找回密码 // 用户登录/注销/找回密码
@@ -14,11 +16,19 @@ type Auth struct {
//-------- //--------
// 登录 // 登录
func (c Auth) Login(email string) revel.Result { func (c Auth) Login(email, from string) revel.Result {
c.RenderArgs["title"] = c.Message("login") c.RenderArgs["title"] = c.Message("login")
c.RenderArgs["subTitle"] = c.Message("login") c.RenderArgs["subTitle"] = c.Message("login")
c.RenderArgs["email"] = email c.RenderArgs["email"] = email
c.RenderArgs["openRegister"] = openRegister c.RenderArgs["from"] = from
c.RenderArgs["openRegister"] = configService.IsOpenRegister()
sessionId := c.Session.Id()
if sessionService.LoginTimesIsOver(sessionId) {
c.RenderArgs["needCaptcha"] = true
}
c.SetLocale()
if c.Has("demo") { if c.Has("demo") {
c.RenderArgs["demo"] = true c.RenderArgs["demo"] = true
@@ -26,78 +36,115 @@ func (c Auth) Login(email string) revel.Result {
} }
return c.RenderTemplate("home/login.html") return c.RenderTemplate("home/login.html")
} }
func (c Auth) DoLogin(email, pwd string) revel.Result {
userInfo := authService.Login(email, pwd) // 为了demo和register
if userInfo.Email != "" { 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) c.SetSession(userInfo)
// 必须要redirect, 不然用户刷新会重复提交登录信息 sessionService.ClearLoginTimes(sessionId)
// return c.Redirect("/")
return c.RenderJson(info.Re{Ok: true}) 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 { func (c Auth) Logout() revel.Result {
sessionId := c.Session.Id()
sessionService.Clear(sessionId)
c.ClearSession() c.ClearSession()
return c.Redirect("/login") return c.Redirect("/login")
} }
// 体验一下 // 体验一下
func (c Auth) Demo() revel.Result { func (c Auth) Demo() revel.Result {
c.DoLogin("demo@leanote.com", "demo@leanote.com") 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 c.Redirect("/note")
}
return nil
} }
//-------- //--------
// 注册 // 注册
func (c Auth) Register() revel.Result { func (c Auth) Register(from, iu string) revel.Result {
if !openRegister { if !configService.IsOpenRegister() {
return c.Redirect("/index") return c.Redirect("/index")
} }
c.SetLocale()
c.RenderArgs["from"] = from
c.RenderArgs["iu"] = iu
c.RenderArgs["title"] = c.Message("register") c.RenderArgs["title"] = c.Message("register")
c.RenderArgs["subTitle"] = c.Message("register") c.RenderArgs["subTitle"] = c.Message("register")
return c.RenderTemplate("home/register.html") return c.RenderTemplate("home/register.html")
} }
func (c Auth) DoRegister(email, pwd string) revel.Result { func (c Auth) DoRegister(email, pwd, iu string) revel.Result {
if !openRegister { if !configService.IsOpenRegister() {
return c.Redirect("/index") return c.Redirect("/index")
} }
re := info.NewRe(); re := info.NewRe()
if email == "" { if re.Ok, re.Msg = Vd("email", email); !re.Ok {
re.Msg = c.Message("inputEmail") return c.RenderRe(re)
return c.RenderJson(re) }
} else if !IsEmail(email) { if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
re.Msg = c.Message("wrongEmail") return c.RenderRe(re)
return c.RenderJson(re)
} }
// 密码 email = strings.ToLower(email)
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)
}
// 注册 // 注册
re.Ok, re.Msg = authService.Register(email, pwd) re.Ok, re.Msg = authService.Register(email, pwd, iu)
// 注册成功, 则立即登录之 // 注册成功, 则立即登录之
if re.Ok { 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 { func (c Auth) FindPassword() revel.Result {
c.SetLocale()
c.RenderArgs["title"] = c.Message("findPassword") c.RenderArgs["title"] = c.Message("findPassword")
c.RenderArgs["subTitle"] = c.Message("findPassword") c.RenderArgs["subTitle"] = c.Message("findPassword")
return c.RenderTemplate("home/find_password.html") return c.RenderTemplate("home/find_password.html")
@@ -108,14 +155,16 @@ func (c Auth) DoFindPassword(email string) revel.Result {
re.Ok = true re.Ok = true
return c.RenderJson(re) return c.RenderJson(re)
} }
// 点击链接后, 先验证之 // 点击链接后, 先验证之
func (c Auth) FindPassword2(token string) revel.Result { func (c Auth) FindPassword2(token string) revel.Result {
c.SetLocale()
c.RenderArgs["title"] = c.Message("findPassword") c.RenderArgs["title"] = c.Message("findPassword")
c.RenderArgs["subTitle"] = c.Message("findPassword") c.RenderArgs["subTitle"] = c.Message("findPassword")
if token == "" { if token == "" {
return c.RenderTemplate("find_password2_timeout.html") return c.RenderTemplate("find_password2_timeout.html")
} }
ok, _, findPwd := tokenService.VerifyToken(token, info.TokenPwd); ok, _, findPwd := tokenService.VerifyToken(token, info.TokenPwd)
if !ok { if !ok {
return c.RenderTemplate("home/find_password2_timeout.html") return c.RenderTemplate("home/find_password2_timeout.html")
} }
@@ -126,16 +175,16 @@ func (c Auth) FindPassword2(token string) revel.Result {
return c.RenderTemplate("home/find_password2.html") return c.RenderTemplate("home/find_password2.html")
} }
// 找回密码修改密码 // 找回密码修改密码
func (c Auth) FindPasswordUpdate(token, pwd string) revel.Result { func (c Auth) FindPasswordUpdate(token, pwd string) revel.Result {
re := info.NewRe(); re := info.NewRe()
re.Ok, re.Msg = IsGoodPwd(pwd) if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
if !re.Ok { return c.RenderRe(re)
return c.RenderJson(re)
} }
// 修改之 // 修改之
re.Ok, re.Msg = pwdService.UpdatePwd(token, pwd) re.Ok, re.Msg = pwdService.UpdatePwd(token, pwd)
return c.RenderJson(re) return c.RenderRe(re)
} }
+111 -18
View File
@@ -1,12 +1,15 @@
package controllers package controllers
import ( import (
"github.com/revel/revel"
"labix.org/v2/mgo/bson"
"encoding/json" "encoding/json"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
// "io/ioutil" "github.com/leanote/leanote/app/lea/i18n"
// "fmt" "github.com/revel/revel"
"gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
// "fmt"
"bytes"
"math" "math"
"strconv" "strconv"
"strings" "strings"
@@ -17,6 +20,11 @@ type BaseController struct {
*revel.Controller *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 { func (c BaseController) GetUserId() string {
if userId, ok := c.Session["UserId"]; ok { if userId, ok := c.Session["UserId"]; ok {
return userId return userId
@@ -54,15 +62,21 @@ func (c BaseController) GetUsername() string {
// 得到用户信息 // 得到用户信息
func (c BaseController) GetUserInfo() info.User { func (c BaseController) GetUserInfo() info.User {
if userId, ok := c.Session["UserId"]; ok && userId != "" { if userId, ok := c.Session["UserId"]; ok && userId != "" {
return userService.GetUserInfo(userId)
/*
notebookWidth, _ := strconv.Atoi(c.Session["NotebookWidth"]) notebookWidth, _ := strconv.Atoi(c.Session["NotebookWidth"])
noteListWidth, _ := strconv.Atoi(c.Session["NoteListWidth"]) noteListWidth, _ := strconv.Atoi(c.Session["NoteListWidth"])
mdEditorWidth, _ := strconv.Atoi(c.Session["MdEditorWidth"])
LogJ(c.Session)
user := info.User{UserId: bson.ObjectIdHex(userId), user := info.User{UserId: bson.ObjectIdHex(userId),
Email: c.Session["Email"], Email: c.Session["Email"],
Logo: c.Session["Logo"],
Username: c.Session["Username"], Username: c.Session["Username"],
UsernameRaw: c.Session["UsernameRaw"], UsernameRaw: c.Session["UsernameRaw"],
Theme: c.Session["Theme"], Theme: c.Session["Theme"],
NotebookWidth: notebookWidth, NotebookWidth: notebookWidth,
NoteListWidth: noteListWidth, NoteListWidth: noteListWidth,
MdEditorWidth: mdEditorWidth,
} }
if c.Session["Verified"] == "1" { if c.Session["Verified"] == "1" {
user.Verified = true user.Verified = true
@@ -71,10 +85,26 @@ func (c BaseController) GetUserInfo() info.User {
user.LeftIsMin = true user.LeftIsMin = true
} }
return user return user
*/
} }
return info.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) { func (c BaseController) SetSession(userInfo info.User) {
if userInfo.UserId.Hex() != "" { if userInfo.UserId.Hex() != "" {
c.Session["UserId"] = userInfo.UserId.Hex() c.Session["UserId"] = userInfo.UserId.Hex()
@@ -82,6 +112,7 @@ func (c BaseController) SetSession(userInfo info.User) {
c.Session["Username"] = userInfo.Username c.Session["Username"] = userInfo.Username
c.Session["UsernameRaw"] = userInfo.UsernameRaw c.Session["UsernameRaw"] = userInfo.UsernameRaw
c.Session["Theme"] = userInfo.Theme c.Session["Theme"] = userInfo.Theme
c.Session["Logo"] = userInfo.Logo
c.Session["NotebookWidth"] = strconv.Itoa(userInfo.NotebookWidth) c.Session["NotebookWidth"] = strconv.Itoa(userInfo.NotebookWidth)
c.Session["NoteListWidth"] = strconv.Itoa(userInfo.NoteListWidth) c.Session["NoteListWidth"] = strconv.Itoa(userInfo.NoteListWidth)
@@ -114,7 +145,7 @@ func (c BaseController) UpdateSession(key, value string) {
// 返回json // 返回json
func (c BaseController) Json(i interface{}) string { func (c BaseController) Json(i interface{}) string {
// b, _ := json.MarshalIndent(i, "", " ") // b, _ := json.MarshalIndent(i, "", " ")
b, _ := json.Marshal(i) b, _ := json.Marshal(i)
return string(b) return string(b)
} }
@@ -132,9 +163,9 @@ func (c BaseController) GetPage() int {
// 判断是否含有某参数 // 判断是否含有某参数
func (c BaseController) Has(key string) bool { func (c BaseController) Has(key string) bool {
if _, ok := c.Params.Values[key]; ok { 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 { 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 { func (c BaseController) E404() revel.Result {
c.RenderArgs["title"] = "404"; c.RenderArgs["title"] = "404"
return c.NotFound("", nil) return c.NotFound("", nil)
} }
// 设置本地 // 设置本地
func (c BaseController) SetLocale() { func (c BaseController) SetLocale() string {
locale := string(c.Request.Locale) // zh-CN 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 lang := locale
if strings.Contains(locale, "-") { if !i18n.HasLang(locale) {
pos := strings.Index(locale, "-") lang = i18n.GetDefaultLang()
lang = locale[0:pos]
} }
if lang != "zh" && lang != "en" { c.RenderArgs["locale"] = lang
lang = "en"; c.RenderArgs["siteUrl"] = configService.GetSiteUrl()
}
c.RenderArgs["locale"] = lang; c.RenderArgs["blogUrl"] = configService.GetBlogUrl()
c.RenderArgs["leaUrl"] = configService.GetLeaUrl()
c.RenderArgs["noteUrl"] = configService.GetNoteUrl()
return lang
} }
// 设置userInfo // 设置userInfo
func (c BaseController) SetUserInfo() { func (c BaseController) SetUserInfo() info.User {
userInfo := c.GetUserInfo() userInfo := c.GetUserInfo()
c.RenderArgs["userInfo"] = userInfo c.RenderArgs["userInfo"] = userInfo
if userInfo.Username == configService.GetAdminUsername() {
c.RenderArgs["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,
RenderArgs: c.RenderArgs, // 把args给它
}
var buffer bytes.Buffer
tpl.Template.Render(&buffer, c.RenderArgs)
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
+44
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()
}
+207 -35
View File
@@ -2,11 +2,16 @@ package controllers
import ( import (
"github.com/revel/revel" "github.com/revel/revel"
// "encoding/json" // "encoding/json"
. "github.com/leanote/leanote/app/lea" "fmt"
"github.com/leanote/leanote/app/info" "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" "io/ioutil"
"os" "os"
// "strconv"
"strings"
) )
// 首页 // 首页
@@ -14,44 +19,82 @@ type File struct {
BaseController BaseController
} }
// 上传图片 editor // 上传的是博客logo
func (c File) UploadImage(renderHtml string) revel.Result { // TODO logo不要设置权限, 另外的目录
if renderHtml == "" { func (c File) UploadBlogLogo() revel.Result {
renderHtml = "file/image.html" re := c.uploadImage("blogLogo", "")
}
re := c.uploadImage(); c.RenderArgs["fileUrlPath"] = re.Id
c.RenderArgs["fileUrlPath"] = siteUrl + re.Id
c.RenderArgs["resultCode"] = re.Code c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["resultMsg"] = re.Msg c.RenderArgs["resultMsg"] = re.Msg
return c.RenderTemplate(renderHtml) return c.RenderTemplate("file/blog_logo.html")
} }
// 上传的是博客logo // 拖拉上传, pasteImage
func (c File) UploadBlogLogo() revel.Result { // noteId 是为了判断是否是协作的note, 如果是则需要复制一份到note owner中
return c.UploadImage("file/blog_logo.html"); 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)
} }
// 拖拉上传 // 头像设置
func (c File) UploadImageJson(renderHtml string) revel.Result { func (c File) UploadAvatar() revel.Result {
re := c.uploadImage(); re := c.uploadImage("logo", "")
re.Id = siteUrl + re.Id
// re.Id = re.Id c.RenderArgs["fileUrlPath"] = re.Id
c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["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)
return c.RenderJson(re) return c.RenderJson(re)
} }
// 上传图片, 公用方法 // 上传图片, 公用方法
func (c File) uploadImage() (re info.Re) { // upload image common func
func (c File) uploadImage(from, albumId string) (re info.Re) {
var fileUrlPath = "" var fileUrlPath = ""
var fileId = ""
var resultCode = 0 // 1表示正常 var resultCode = 0 // 1表示正常
var resultMsg = "内部错误" // 错误信息 var resultMsg = "error" // 错误信息
var Ok = false
defer func() { defer func() {
re.Id = fileUrlPath re.Id = fileId // 只是id, 没有其它信息
re.Code = resultCode re.Code = resultCode
re.Msg = resultMsg re.Msg = resultMsg
re.Ok = Ok
}() }()
file, handel, err := c.Request.FormFile("file") file, handel, err := c.Request.FormFile("file")
@@ -59,36 +102,66 @@ func (c File) uploadImage() (re info.Re) {
return re return re
} }
defer file.Close() defer file.Close()
// 生成上传路径 // 生成上传路径
fileUrlPath = "/upload/" + c.GetUserId() + "/images" newGuid := NewGuid()
dir := revel.BasePath + "/public/" + fileUrlPath
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) err = os.MkdirAll(dir, 0755)
if err != nil { if err != nil {
Log(err)
return re return re
} }
// 生成新的文件名 // 生成新的文件名
filename := handel.Filename filename := handel.Filename
_, ext := SplitFilename(filename)
if(ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg") { var ext string
resultMsg = "不是图片" if from == "pasteImage" {
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 = "Please upload image"
return re return re
} }
filename = NewGuid() + ext }
filename = newGuid + ext
data, err := ioutil.ReadAll(file) data, err := ioutil.ReadAll(file)
if err != nil { if err != nil {
LogJ(err) LogJ(err)
return re 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? // > 2M?
if(len(data) > 5 * 1024 * 1024) { if float64(len(data)) > maxFileSize*float64(1024*1024) {
resultCode = 0 resultCode = 0
resultMsg = "图片大于2M" resultMsg = fmt.Sprintf("The file Size is bigger than %vM", maxFileSize)
return re return re
} }
toPath := dir + "/" + filename; toPath := dir + "/" + filename
err = ioutil.WriteFile(toPath, data, 0777) err = ioutil.WriteFile(toPath, data, 0777)
if err != nil { if err != nil {
LogJ(err) LogJ(err)
@@ -96,10 +169,109 @@ func (c File) uploadImage() (re info.Re) {
} }
// 改变成gif图片 // 改变成gif图片
_, toPathGif := TransToGif(toPath, 0, true) _, toPathGif := TransToGif(toPath, 0, true)
filename = GetFilename(toPathGif)
fileUrlPath += "/" + GetFilename(toPathGif) filesize := GetFilesize(toPathGif)
fileUrlPath += "/" + filename
resultCode = 1 resultCode = 1
resultMsg = "上传成功!" resultMsg = "Upload Success!"
// File
fileInfo := info.File{Name: filename,
Title: handel.Filename,
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 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)
}
func (c File) UpdateImageTitle(fileId, title string) revel.Result {
re := info.NewRe()
re.Ok = fileService.UpdateImageTitle(c.GetUserId(), fileId, title)
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)
}
//-----------
// 输出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()
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)
}
filesize, filename, _, ok := netutil.WriteUrl(src, dir)
if !ok {
re.Msg = "copy error"
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)
}
+14 -6
View File
@@ -1,9 +1,9 @@
package controllers package controllers
import ( import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea" "github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea"
) )
// 首页 // 首页
@@ -12,14 +12,22 @@ type Index struct {
BaseController BaseController
} }
func (c Index) Default() revel.Result {
if configService.HomePageIsAdminsBlog() {
blog := Blog{c.BaseController}
return blog.Index(configService.GetAdminUsername())
}
return c.Index()
}
// leanote展示页, 没有登录的, 或已登录明确要进该页的 // leanote展示页, 没有登录的, 或已登录明确要进该页的
func (c Index) Index() revel.Result { func (c Index) Index() revel.Result {
c.SetUserInfo() c.SetUserInfo()
c.RenderArgs["title"] = "leanote" c.RenderArgs["title"] = "leanote"
c.RenderArgs["openRegister"] = openRegister c.RenderArgs["openRegister"] = configService.GlobalStringConfigs["openRegister"]
c.SetLocale() c.SetLocale()
return c.RenderTemplate("home/index.html"); return c.RenderTemplate("home/index.html")
} }
// 建议 // 建议
@@ -29,8 +37,8 @@ func (c Index) Suggestion(addr, suggestion string) revel.Result {
// 发给我 // 发给我
go func() { go func() {
SendToLeanote("建议", "建议", "UserId: " + c.GetUserId() + " <br /> Suggestions: " + suggestion) emailService.SendEmail("leanote@leanote.com", "建议", "UserId: "+c.GetUserId()+" <br /> Suggestions: "+suggestion)
}(); }()
return c.RenderJson(re) return c.RenderJson(re)
} }
-42
View File
@@ -1,42 +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/index.html");
}
func (c Mobile) Logout() revel.Result {
c.ClearSession()
return c.RenderTemplate("mobile/login.html");
}
+322 -90
View File
@@ -2,15 +2,20 @@ package controllers
import ( import (
"github.com/revel/revel" "github.com/revel/revel"
// "encoding/json" // "encoding/json"
"labix.org/v2/mgo/bson"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/lea/html2image"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
// "os" . "github.com/leanote/leanote/app/lea"
// "github.com/leanote/leanote/app/types" "gopkg.in/mgo.v2/bson"
// "io/ioutil" "os"
// "fmt" "os/exec"
"regexp"
"strings"
"time"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
"fmt"
// "bytes"
// "os"
) )
type Note struct { type Note struct {
@@ -20,10 +25,9 @@ type Note struct {
// 笔记首页, 判断是否已登录 // 笔记首页, 判断是否已登录
// 已登录, 得到用户基本信息(notebook, shareNotebook), 跳转到index.html中 // 已登录, 得到用户基本信息(notebook, shareNotebook), 跳转到index.html中
// 否则, 转向登录页面 // 否则, 转向登录页面
func (c Note) Index() revel.Result { func (c Note) Index(noteId, online string) revel.Result {
c.SetLocale() c.SetLocale()
userInfo := c.GetUserAndBlogUrl()
userInfo := c.GetUserInfo()
userId := userInfo.UserId.Hex() userId := userInfo.UserId.Hex()
@@ -32,7 +36,7 @@ func (c Note) Index() revel.Result {
return c.Redirect("/login") return c.Redirect("/login")
} }
c.RenderArgs["openRegister"] = openRegister c.RenderArgs["openRegister"] = configService.IsOpenRegister()
// 已登录了, 那么得到所有信息 // 已登录了, 那么得到所有信息
notebooks := notebookService.GetNotebooks(userId) notebooks := notebookService.GetNotebooks(userId)
@@ -41,30 +45,92 @@ func (c Note) Index() revel.Result {
// 还需要按时间排序(DESC)得到notes // 还需要按时间排序(DESC)得到notes
notes := []info.Note{} notes := []info.Note{}
noteContent := info.NoteContent{} noteContent := info.NoteContent{}
if len(notebooks) > 0 { if len(notebooks) > 0 {
// _, notes = noteService.ListNotes(c.GetUserId(), "", false, c.GetPage(), pageSize, defaultSortField, false, false); // noteId是否存在
// 变成最新 // 是否传入了正确的noteId
_, notes = noteService.ListNotes(c.GetUserId(), "", false, c.GetPage(), 50, defaultSortField, false, false); 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.RenderArgs["curNoteId"] = noteId
c.RenderArgs["curNotebookId"] = note.NotebookId.Hex()
// 打开的是共享的笔记, 那么判断是否是共享给我的默认笔记
if noteOwner != c.GetUserId() {
if shareService.HasReadPerm(noteOwner, c.GetUserId(), noteId) {
// 不要获取notebook下的笔记
// 在前端下发请求
c.RenderArgs["curSharedNoteNotebookId"] = note.NotebookId.Hex()
c.RenderArgs["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.RenderArgs["latestNotes"] = latestNotes
}
// 没有传入笔记
// 那么得到最新笔记
if !hasRightNoteId {
_, notes = noteService.ListNotes(c.GetUserId(), "", false, c.GetPage(), 50, defaultSortField, false, false)
if len(notes) > 0 { if len(notes) > 0 {
noteContent = noteService.GetNoteContent(notes[0].NoteId.Hex(), userId) noteContent = noteService.GetNoteContent(notes[0].NoteId.Hex(), userId)
c.RenderArgs["curNoteId"] = notes[0].NoteId.Hex()
} }
} }
}
// 当然, 还需要得到第一个notes的content // 当然, 还需要得到第一个notes的content
//... //...
c.RenderArgs["isAdmin"] = configService.GetAdminUsername() == userInfo.Username
c.RenderArgs["userInfo"] = userInfo c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["userInfoJson"] = c.Json(userInfo) c.RenderArgs["notebooks"] = notebooks
c.RenderArgs["notebooks"] = c.Json(notebooks) c.RenderArgs["shareNotebooks"] = shareNotebooks // note信息在notes列表中
c.RenderArgs["shareNotebooks"] = c.Json(shareNotebooks) c.RenderArgs["sharedUserInfos"] = sharedUserInfos
c.RenderArgs["sharedUserInfos"] = c.Json(sharedUserInfos)
c.RenderArgs["notes"] = c.Json(notes) c.RenderArgs["notes"] = notes
c.RenderArgs["noteContentJson"] = c.Json(noteContent) c.RenderArgs["noteContentJson"] = noteContent
c.RenderArgs["noteContent"] = noteContent.Content c.RenderArgs["noteContent"] = noteContent.Content
c.RenderArgs["tagsJson"] = c.Json(tagService.GetTags(c.GetUserId())) c.RenderArgs["tags"] = tagService.GetTags(c.GetUserId())
if isDev, _ := revel.Config.Bool("mode.dev"); isDev { c.RenderArgs["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") return c.RenderTemplate("note/note-dev.html")
} else { } else {
return c.RenderTemplate("note/note.html") return c.RenderTemplate("note/note.html")
@@ -75,47 +141,33 @@ func (c Note) Index() revel.Result {
// 已登录, 得到用户基本信息(notebook, shareNotebook), 跳转到index.html中 // 已登录, 得到用户基本信息(notebook, shareNotebook), 跳转到index.html中
// 否则, 转向登录页面 // 否则, 转向登录页面
func (c Note) ListNotes(notebookId string) revel.Result { func (c Note) ListNotes(notebookId string) revel.Result {
_, notes := noteService.ListNotes(c.GetUserId(), notebookId, false, c.GetPage(), pageSize, defaultSortField, false, false); _, notes := noteService.ListNotes(c.GetUserId(), notebookId, false, c.GetPage(), pageSize, defaultSortField, false, false)
return c.RenderJson(notes) return c.RenderJson(notes)
} }
// 得到trash // 得到trash
func (c Note) ListTrashNotes() revel.Result { func (c Note) ListTrashNotes() revel.Result {
_, notes := noteService.ListNotes(c.GetUserId(), "", true, c.GetPage(), pageSize, defaultSortField, false, false); _, notes := noteService.ListNotes(c.GetUserId(), "", true, c.GetPage(), pageSize, defaultSortField, false, false)
return c.RenderJson(notes) return c.RenderJson(notes)
} }
// 得到note和内容
func (c Note) GetNoteAndContent(noteId string) revel.Result {
return c.RenderJson(noteService.GetNoteAndContent(noteId, c.GetUserId()))
}
// 得到内容 // 得到内容
func (c Note) GetNoteContent(noteId string) revel.Result { func (c Note) GetNoteContent(noteId string) revel.Result {
noteContent := noteService.GetNoteContent(noteId, c.GetUserId()) 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 // 这里不能用json, 要用post
func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result { func (c Note) UpdateNoteOrContent(noteOrContent info.NoteOrContent) revel.Result {
// 新添加note // 新添加note
LogJ(noteOrContent)
if noteOrContent.IsNew { if noteOrContent.IsNew {
userId := c.GetObjectUserId(); userId := c.GetObjectUserId()
myUserId := userId // myUserId := userId
// 为共享新建? // 为共享新建?
if noteOrContent.FromUserId != "" { if noteOrContent.FromUserId != "" {
userId = bson.ObjectIdHex(noteOrContent.FromUserId) userId = bson.ObjectIdHex(noteOrContent.FromUserId)
@@ -125,19 +177,19 @@ func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result {
NoteId: bson.ObjectIdHex(noteOrContent.NoteId), NoteId: bson.ObjectIdHex(noteOrContent.NoteId),
NotebookId: bson.ObjectIdHex(noteOrContent.NotebookId), NotebookId: bson.ObjectIdHex(noteOrContent.NotebookId),
Title: noteOrContent.Title, Title: noteOrContent.Title,
Tags: noteOrContent.Tags, Tags: strings.Split(noteOrContent.Tags, ","),
Desc: noteOrContent.Desc, Desc: noteOrContent.Desc,
ImgSrc: noteOrContent.ImgSrc, ImgSrc: noteOrContent.ImgSrc,
IsBlog: noteOrContent.IsBlog, IsBlog: noteOrContent.IsBlog,
IsMarkdown: noteOrContent.IsMarkdown, IsMarkdown: noteOrContent.IsMarkdown,
}; }
noteContent := info.NoteContent{NoteId: note.NoteId, noteContent := info.NoteContent{NoteId: note.NoteId,
UserId: userId, UserId: userId,
IsBlog: note.IsBlog, IsBlog: note.IsBlog,
Content: noteOrContent.Content, Content: noteOrContent.Content,
Abstract: noteOrContent.Abstract}; Abstract: noteOrContent.Abstract}
note = noteService.AddNoteAndContent(note, noteContent, myUserId) note = noteService.AddNoteAndContentForController(note, noteContent, c.GetUserId())
return c.RenderJson(note) return c.RenderJson(note)
} }
@@ -147,61 +199,102 @@ func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result {
// Desc前台传来 // Desc前台传来
if c.Has("Desc") { if c.Has("Desc") {
needUpdateNote = true needUpdateNote = true
noteUpdate["Desc"] = noteOrContent.Desc; noteUpdate["Desc"] = noteOrContent.Desc
} }
if c.Has("ImgSrc") { if c.Has("ImgSrc") {
needUpdateNote = true needUpdateNote = true
noteUpdate["ImgSrc"] = noteOrContent.ImgSrc; noteUpdate["ImgSrc"] = noteOrContent.ImgSrc
} }
if c.Has("Title") { if c.Has("Title") {
needUpdateNote = true needUpdateNote = true
noteUpdate["Title"] = noteOrContent.Title; noteUpdate["Title"] = noteOrContent.Title
} }
if c.Has("Tags[]") { if c.Has("Tags") {
needUpdateNote = true needUpdateNote = true
noteUpdate["Tags"] = noteOrContent.Tags; noteUpdate["Tags"] = strings.Split(noteOrContent.Tags, ",")
} }
// web端不控制
if needUpdateNote { if needUpdateNote {
noteService.UpdateNote(noteOrContent.UserId, c.GetUserId(), noteService.UpdateNote(c.GetUserId(),
noteOrContent.NoteId, noteUpdate) noteOrContent.NoteId, noteUpdate, -1)
} }
//------------- //-------------
afterContentUsn := 0
contentOk := false
contentMsg := ""
if c.Has("Content") { if c.Has("Content") {
noteService.UpdateNoteContent(noteOrContent.UserId, c.GetUserId(), // noteService.UpdateNoteContent(noteOrContent.UserId, c.GetUserId(),
noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract) // noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract)
contentOk, contentMsg, afterContentUsn = noteService.UpdateNoteContent(c.GetUserId(),
noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract,
needUpdateNote, -1, time.Now())
} }
Log(afterContentUsn)
Log(contentOk)
Log(contentMsg)
return c.RenderJson(true) return c.RenderJson(true)
} }
// 删除note/ 删除别人共享给我的笔记 // 删除note/ 删除别人共享给我的笔记
// userId 是note.UserId // userId 是note.UserId
func (c Note) DeleteNote(noteId, userId string, isShared bool) revel.Result { func (c Note) DeleteNote(noteIds []string, isShared bool) revel.Result {
if(!isShared) { if !isShared {
return c.RenderJson(trashService.DeleteNote(noteId, c.GetUserId())); 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 { 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 // 移动note
func (c Note) MoveNote(noteId, notebookId string) revel.Result { func (c Note) MoveNote(noteIds []string, notebookId string) revel.Result {
return c.RenderJson(noteService.MoveNote(noteId, notebookId, c.GetUserId())); userId := c.GetUserId()
for _, noteId := range noteIds {
noteService.MoveNote(noteId, notebookId, userId)
}
return c.RenderJson(true)
} }
// 复制note // 复制note
func (c Note) CopyNote(noteId, notebookId string) revel.Result { func (c Note) CopyNote(noteIds []string, notebookId string) revel.Result {
return c.RenderJson(noteService.CopyNote(noteId, notebookId, c.GetUserId())); 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 { func (c Note) CopySharedNote(noteIds []string, notebookId, fromUserId string) revel.Result {
return c.RenderJson(noteService.CopySharedNote(noteId, notebookId, fromUserId, c.GetUserId())); 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)
} }
//------------ //------------
@@ -211,37 +304,176 @@ func (c Note) SearchNote(key string) revel.Result {
_, blogs := noteService.SearchNote(key, c.GetUserId(), c.GetPage(), pageSize, "UpdatedTime", false, false) _, blogs := noteService.SearchNote(key, c.GetUserId(), c.GetPage(), pageSize, "UpdatedTime", false, false)
return c.RenderJson(blogs) return c.RenderJson(blogs)
} }
// 通过tags搜索 // 通过tags搜索
func (c Note) SearchNoteByTags(tags []string) revel.Result { func (c Note) SearchNoteByTags(tags []string) revel.Result {
_, blogs := noteService.SearchNoteByTags(tags, c.GetUserId(), c.GetPage(), pageSize, "UpdatedTime", false) _, blogs := noteService.SearchNoteByTags(tags, c.GetUserId(), c.GetPage(), pageSize, "UpdatedTime", false)
return c.RenderJson(blogs) return c.RenderJson(blogs)
} }
//----------------- // 生成PDF
// html2image func (c Note) ToPdf(noteId, appKey string) revel.Result {
func (c Note) Html2Image(noteId 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)
}
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.RenderArgs["blog"] = note
c.RenderArgs["content"] = contentStr
c.RenderArgs["userInfo"] = userInfo
userBlog := blogService.GetUserBlog(noteUserId)
c.RenderArgs["userBlog"] = userBlog
return c.RenderTemplate("file/pdf.html")
}
// 导出成PDF
func (c Note) ExportPdf(noteId string) revel.Result {
re := info.NewRe() re := info.NewRe()
userId := c.GetUserId() userId := c.GetUserId()
note := noteService.GetNote(noteId, userId) note := noteService.GetNoteById(noteId)
if note.NoteId == "" { if note.NoteId == "" {
return c.RenderJson(re) re.Msg = "No Note"
return c.RenderText("error")
}
noteUserId := note.UserId.Hex()
// 是否有权限
if noteUserId != userId {
// 是否是有权限协作的
if !note.IsBlog && !shareService.HasReadPerm(noteUserId, userId, noteId) {
re.Msg = "No Perm"
return c.RenderText("No Perm")
}
} }
content := noteService.GetNoteContent(noteId, userId)
// path 判断是否需要重新生成之 // path 判断是否需要重新生成之
fileUrlPath := "/upload/" + userId + "/images/weibo" guid := NewGuid()
dir := revel.BasePath + "/public/" + fileUrlPath fileUrlPath := "files/export_pdf"
if !ClearDir(dir) { dir := revel.BasePath + "/" + fileUrlPath
return c.RenderJson(re) if !MkdirAll(dir) {
return c.RenderText("error, no dir")
} }
filename := guid + ".pdf"
filename := note.NoteId.Hex() + ".png";
path := dir + "/" + filename path := dir + "/" + filename
// 生成之 // leanote.com的secret
html2image.ToImage(userId, c.GetUsername(), noteId, note.Title, content.Content, path) appKey, _ := revel.Config.String("app.secretLeanote")
if appKey == "" {
appKey, _ = revel.Config.String("app.secret")
}
re.Ok = true // 生成之
re.Id = fileUrlPath + "/" + filename binPath := configService.GetGlobalStringConfig("exportPdfBinPath")
return c.RenderJson(re) // 默认路径
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)
} }
+39 -16
View File
@@ -1,29 +1,24 @@
package controllers package controllers
import ( import (
"github.com/revel/revel" "encoding/json"
// "encoding/json"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
"labix.org/v2/mgo/bson" "github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea" "gopkg.in/mgo.v2/bson"
// "io/ioutil" // . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
) )
type Notebook struct { type Notebook struct {
BaseController BaseController
} }
// 得到笔记本
// 该用户下的
func (c Notebook) GetNotebooks() {
}
func (c Notebook) Index(notebook info.Notebook, i int, name string) revel.Result { 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 { func (c Notebook) GetNotebooks() revel.Result {
re := notebookService.GetNotebooks(c.GetUserId()) re := notebookService.GetNotebooks(c.GetUserId())
return c.RenderJson(re) return c.RenderJson(re)
} }
@@ -34,24 +29,52 @@ func (c Notebook) DeleteNotebook(notebookId string) revel.Result {
} }
// 添加notebook // 添加notebook
func (c Notebook) AddNotebook(notebookId, title string) revel.Result { func (c Notebook) AddNotebook(notebookId, title, parentNotebookId string) revel.Result {
notebook := info.Notebook{NotebookId: bson.ObjectIdHex(notebookId), notebook := info.Notebook{NotebookId: bson.ObjectIdHex(notebookId),
Title: title, Title: title,
Seq: -1, Seq: -1,
UserId: c.GetObjectUserId()} UserId: c.GetObjectUserId()}
re := notebookService.AddNotebook(notebook) if parentNotebookId != "" {
notebook.ParentNotebookId = bson.ObjectIdHex(parentNotebookId)
}
if(re) { re, notebook := notebookService.AddNotebook(notebook)
if re {
return c.RenderJson(notebook) return c.RenderJson(notebook)
} else { } else {
return c.RenderJson(false) return c.RenderJson(false)
} }
} }
// 修改标题 // 修改标题
func (c Notebook) UpdateNotebookTitle(notebookId, title string) revel.Result { 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)
} }
-70
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")
}
+103
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.RenderArgs["isPreview"] = true
c.RenderArgs["themeId"] = themeId
c.RenderArgs["themeInfoPreview"] = theme.Info
c.RenderArgs["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.RenderArgs, 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.RenderArgs, c.getPreviewThemeAbsolutePath(""))
// }
// return blog.RenderTemplate("tag_posts.html", c.RenderArgs, 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.RenderArgs, 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.RenderArgs, 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.RenderArgs, 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.RenderArgs, 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.RenderArgs, c.getPreviewThemeAbsolutePath(""))
}
+75 -25
View File
@@ -2,13 +2,13 @@ package controllers
import ( import (
"github.com/revel/revel" "github.com/revel/revel"
// "encoding/json" // "encoding/json"
// "labix.org/v2/mgo/bson" // "gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
// "github.com/leanote/leanote/app/types" . "github.com/leanote/leanote/app/lea"
// "io/ioutil" // "github.com/leanote/leanote/app/types"
// "fmt" // "io/ioutil"
// "fmt"
) )
type Share struct { type Share struct {
@@ -19,13 +19,13 @@ type Share struct {
func (c Share) AddShareNote(noteId string, emails []string, perm int) revel.Result { func (c Share) AddShareNote(noteId string, emails []string, perm int) revel.Result {
status := make(map[string]info.Re, len(emails)) status := make(map[string]info.Re, len(emails))
// 自己不能给自己添加共享 // 自己不能给自己添加共享
myEmail := c.GetEmail(); myEmail := c.GetEmail()
for _, email := range emails { for _, email := range emails {
if email == "" { if email == "" {
continue; continue
} }
if(myEmail != email) { if myEmail != email {
ok, msg, userId := shareService.AddShareNote(noteId, perm, c.GetUserId(), email); ok, msg, userId := shareService.AddShareNote(noteId, perm, c.GetUserId(), email)
status[email] = info.Re{Ok: ok, Msg: msg, Id: userId} status[email] = info.Re{Ok: ok, Msg: msg, Id: userId}
} else { } else {
@@ -40,13 +40,13 @@ func (c Share) AddShareNote(noteId string, emails []string, perm int) revel.Resu
func (c Share) AddShareNotebook(notebookId string, emails []string, perm int) revel.Result { func (c Share) AddShareNotebook(notebookId string, emails []string, perm int) revel.Result {
status := make(map[string]info.Re, len(emails)) status := make(map[string]info.Re, len(emails))
// 自己不能给自己添加共享 // 自己不能给自己添加共享
myEmail := c.GetEmail(); myEmail := c.GetEmail()
for _, email := range emails { for _, email := range emails {
if email == "" { if email == "" {
continue; continue
} }
if(myEmail != email) { if myEmail != email {
ok, msg, userId := shareService.AddShareNotebook(notebookId, perm, c.GetUserId(), email); ok, msg, userId := shareService.AddShareNotebook(notebookId, perm, c.GetUserId(), email)
status[email] = info.Re{Ok: ok, Msg: msg, Id: userId} status[email] = info.Re{Ok: ok, Msg: msg, Id: userId}
} else { } else {
status[email] = info.Re{Ok: false, Msg: "不能分享给自己"} status[email] = info.Re{Ok: false, Msg: "不能分享给自己"}
@@ -62,11 +62,11 @@ func (c Share) ListShareNotes(notebookId, userId string) revel.Result {
// 表示是默认笔记本, 不是某个特定notebook的共享 // 表示是默认笔记本, 不是某个特定notebook的共享
var notes []info.ShareNoteWithPerm var notes []info.ShareNoteWithPerm
if notebookId == "" { if notebookId == "" {
notes = shareService.ListShareNotes(c.GetUserId(), userId, c.GetPage(), pageSize, defaultSortField, false); notes = shareService.ListShareNotes(c.GetUserId(), userId, c.GetPage(), pageSize, defaultSortField, false)
return c.RenderJson(notes) return c.RenderJson(notes)
} else { } else {
// 有notebookId的 // 有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))
} }
} }
@@ -87,8 +87,10 @@ func (c Share) ListNoteShareUserInfo(noteId string) revel.Result {
noteShareUserInfos := shareService.ListNoteShareUserInfo(noteId, c.GetUserId()) noteShareUserInfos := shareService.ListNoteShareUserInfo(noteId, c.GetUserId())
c.RenderArgs["noteOrNotebookShareUserInfos"] = noteShareUserInfos c.RenderArgs["noteOrNotebookShareUserInfos"] = noteShareUserInfos
c.RenderArgs["noteOrNotebookShareGroupInfos"] = shareService.GetNoteShareGroups(noteId, c.GetUserId())
c.RenderArgs["isNote"] = true c.RenderArgs["isNote"] = true
c.RenderArgs["noteOrNotebookId"] = note.NoteId.Hex(); c.RenderArgs["noteOrNotebookId"] = note.NoteId.Hex()
c.RenderArgs["title"] = note.Title c.RenderArgs["title"] = note.Title
return c.RenderTemplate("share/note_notebook_share_user_infos.html") return c.RenderTemplate("share/note_notebook_share_user_infos.html")
@@ -99,8 +101,11 @@ func (c Share) ListNotebookShareUserInfo(notebookId string) revel.Result {
notebookShareUserInfos := shareService.ListNotebookShareUserInfo(notebookId, c.GetUserId()) notebookShareUserInfos := shareService.ListNotebookShareUserInfo(notebookId, c.GetUserId())
c.RenderArgs["noteOrNotebookShareUserInfos"] = notebookShareUserInfos c.RenderArgs["noteOrNotebookShareUserInfos"] = notebookShareUserInfos
c.RenderArgs["noteOrNotebookShareGroupInfos"] = shareService.GetNotebookShareGroups(notebookId, c.GetUserId())
LogJ(c.RenderArgs["noteOrNotebookShareGroupInfos"])
c.RenderArgs["isNote"] = false c.RenderArgs["isNote"] = false
c.RenderArgs["noteOrNotebookId"] = notebook.NotebookId.Hex(); c.RenderArgs["noteOrNotebookId"] = notebook.NotebookId.Hex()
c.RenderArgs["title"] = notebook.Title c.RenderArgs["title"] = notebook.Title
return c.RenderTemplate("share/note_notebook_share_user_infos.html") return c.RenderTemplate("share/note_notebook_share_user_infos.html")
@@ -109,34 +114,79 @@ func (c Share) ListNotebookShareUserInfo(notebookId string) revel.Result {
//------------ //------------
// 改变share note 权限 // 改变share note 权限
func (c Share) UpdateShareNotePerm(noteId string, perm int, toUserId string) revel.Result { 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 权限 // 改变share notebook 权限
func (c Share) UpdateShareNotebookPerm(notebookId string, perm int, toUserId string) revel.Result { 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 // 删除share note
func (c Share) DeleteShareNote(noteId string, toUserId string) revel.Result { 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 // 删除share notebook
func (c Share) DeleteShareNotebook(notebookId string, toUserId string) revel.Result { 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, 被共享方删除 // 删除share note, 被共享方删除
func (c Share) DeleteShareNoteBySharedUser(noteId string, fromUserId string) revel.Result { 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, 被共享方删除 // 删除share notebook, 被共享方删除
func (c Share) DeleteShareNotebookBySharedUser(notebookId string, fromUserId string) revel.Result { 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 // 删除fromUserId分享给我的所有note, notebook
func (c Share) DeleteUserShareNoteAndNotebook(fromUserId string) revel.Result { 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)
} }
+30
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)
}
+54 -90
View File
@@ -2,16 +2,16 @@ package controllers
import ( import (
"github.com/revel/revel" "github.com/revel/revel"
// "encoding/json" // "encoding/json"
// "labix.org/v2/mgo/bson" // "gopkg.in/mgo.v2/bson"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
// "github.com/leanote/leanote/app/types" . "github.com/leanote/leanote/app/lea"
// "io/ioutil" // "github.com/leanote/leanote/app/types"
"fmt" // "io/ioutil"
// "math" // "fmt"
// "os" // "math"
// "path" // "os"
// "path"
"strconv" "strconv"
) )
@@ -19,70 +19,69 @@ type User struct {
BaseController BaseController
} }
func (c User) Account(tab int) revel.Result {
userInfo := c.GetUserInfo()
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["tab"] = tab
c.SetLocale()
return c.RenderTemplate("user/account.html")
}
// 修改用户名, 需要重置session // 修改用户名, 需要重置session
func (c User) UpdateUsername(username string) revel.Result { func (c User) UpdateUsername(username string) revel.Result {
re := info.NewRe(); re := info.NewRe()
// 判断是否满足最基本的, 4位, 不含特殊字符, 大小写无关. email大小写无关 if c.GetUserId() == configService.GetGlobalStringConfig("demoUserId") {
if len(username) < 4 { re.Msg = "cannotUpdateDemo"
re.Ok = false return c.RenderRe(re)
re.Msg = "至少4位"
return c.RenderJson(re);
} }
if !IsUsername(username) {
re.Ok = false if re.Ok, re.Msg = Vd("username", username); !re.Ok {
re.Msg = "不能包含特殊字符" return c.RenderRe(re)
return c.RenderJson(re);
} }
re.Ok, re.Msg = userService.UpdateUsername(c.GetUserId(), username) re.Ok, re.Msg = userService.UpdateUsername(c.GetUserId(), username)
if(re.Ok) { if re.Ok {
c.UpdateSession("Username", username) c.UpdateSession("Username", username)
} }
return c.RenderJson(re); return c.RenderRe(re)
} }
// 修改密码 // 修改密码
func (c User) UpdatePwd(oldPwd, pwd string) revel.Result { func (c User) UpdatePwd(oldPwd, pwd string) revel.Result {
re := info.NewRe(); re := info.NewRe()
if oldPwd == "" { if c.GetUserId() == configService.GetGlobalStringConfig("demoUserId") {
re.Msg = "旧密码错误" re.Msg = "cannotUpdateDemo"
return c.RenderJson(re); return c.RenderRe(re)
} }
if re.Ok, re.Msg = Vd("password", oldPwd); !re.Ok {
re.Ok, re.Msg = IsGoodPwd(pwd) return c.RenderRe(re)
if !re.Ok { }
return c.RenderJson(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) 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 { func (c User) UpdateTheme(theme string) revel.Result {
re := info.NewRe(); re := info.NewRe()
re.Ok = userService.UpdateTheme(c.GetUserId(), theme) re.Ok = userService.UpdateTheme(c.GetUserId(), theme)
if re.Ok { if re.Ok {
c.UpdateSession("Theme", theme) c.UpdateSession("Theme", theme)
} }
return c.RenderJson(re); return c.RenderJson(re)
} }
// 发送邀请链接 // 发送邀请链接
func (c User) SendRegisterEmail(content, toEmail string) revel.Result { func (c User) SendRegisterEmail(content, toEmail string) revel.Result {
re := info.NewRe() re := info.NewRe()
if content == "" || !IsEmail(toEmail) { if content == "" || !IsEmail(toEmail) {
return c.RenderJson(re); return c.RenderJson(re)
} }
// 发送邮件 re.Ok = emailService.SendInviteEmail(c.GetUserInfo(), toEmail, content)
var userInfo = c.GetUserInfo(); return c.RenderJson(re)
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);
} }
//--------------------------- //---------------------------
@@ -90,21 +89,14 @@ func (c User) SendRegisterEmail(content, toEmail string) revel.Result {
// 重新发送激活邮件 // 重新发送激活邮件
func (c User) ReSendActiveEmail() revel.Result { func (c User) ReSendActiveEmail() revel.Result {
re := info.NewRe() re := info.NewRe()
re.Ok = userService.RegisterSendActiveEmail(c.GetUserId(), c.GetEmail()) re.Ok = emailService.RegisterSendActiveEmail(c.GetUserInfo(), 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) return c.RenderJson(re)
} }
// 通过点击链接 // 通过点击链接
// 修改邮箱 // 修改邮箱
func (c User) UpdateEmail(token string) revel.Result { func (c User) UpdateEmail(token string) revel.Result {
c.SetUserInfo(); c.SetUserInfo()
ok, msg, email := userService.UpdateEmail(token) ok, msg, email := userService.UpdateEmail(token)
@@ -123,13 +115,13 @@ func (c User) UpdateEmail(token string) revel.Result {
// 注册后激活邮箱 // 注册后激活邮箱
func (c User) ActiveEmail(token string) revel.Result { func (c User) ActiveEmail(token string) revel.Result {
c.SetUserInfo(); c.SetUserInfo()
ok, msg, email := userService.ActiveEmail(token) ok, msg, email := userService.ActiveEmail(token)
// 需要修改session // 需要修改session
if ok { if ok {
c.UpdateSession("Verified", "1"); c.UpdateSession("Verified", "1")
} }
c.RenderArgs["title"] = "验证邮箱" c.RenderArgs["title"] = "验证邮箱"
@@ -140,45 +132,17 @@ func (c User) ActiveEmail(token string) revel.Result {
return c.RenderTemplate("user/active_email.html") 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)
}
//----------------- //-----------------
// 用户偏爱 // 用户偏爱
func (c User) UpdateColumnWidth(notebookWidth, noteListWidth int) revel.Result { func (c User) UpdateColumnWidth(notebookWidth, noteListWidth, mdEditorWidth int) revel.Result {
re := info.NewRe() re := info.NewRe()
re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth) re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth, mdEditorWidth)
if re.Ok { if re.Ok {
c.UpdateSession("NotebookWidth", strconv.Itoa(notebookWidth)); c.UpdateSession("NotebookWidth", strconv.Itoa(notebookWidth))
c.UpdateSession("NoteListWidth", strconv.Itoa(noteListWidth)); c.UpdateSession("NoteListWidth", strconv.Itoa(noteListWidth))
c.UpdateSession("MdEditorWidth", strconv.Itoa(mdEditorWidth))
LogJ(c.Session)
} }
return c.RenderJson(re) return c.RenderJson(re)
} }
@@ -187,9 +151,9 @@ func (c User) UpdateLeftIsMin(leftIsMin bool) revel.Result {
re.Ok = userService.UpdateLeftIsMin(c.GetUserId(), leftIsMin) re.Ok = userService.UpdateLeftIsMin(c.GetUserId(), leftIsMin)
if re.Ok { if re.Ok {
if leftIsMin { if leftIsMin {
c.UpdateSession("LeftIsMin", "1"); c.UpdateSession("LeftIsMin", "1")
} else { } else {
c.UpdateSession("LeftIsMin", "0"); c.UpdateSession("LeftIsMin", "0")
} }
} }
return c.RenderJson(re) return c.RenderJson(re)
@@ -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.RenderArgs["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)
}
}
@@ -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.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["blogs"] = blogs
c.RenderArgs["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)
}
+39
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.RenderArgs["title"] = "leanote"
c.SetLocale()
c.RenderArgs["countUser"] = userService.CountUser()
c.RenderArgs["countNote"] = noteService.CountNote("")
c.RenderArgs["countBlog"] = noteService.CountBlog("")
return c.RenderTemplate("admin/index.html")
}
// 模板
func (c Admin) T(t string) revel.Result {
c.RenderArgs["str"] = configService.GlobalStringConfigs
c.RenderArgs["arr"] = configService.GlobalArrayConfigs
c.RenderArgs["map"] = configService.GlobalMapConfigs
c.RenderArgs["arrMap"] = configService.GlobalArrMapConfigs
c.RenderArgs["version"] = configService.GetVersion()
return c.RenderTemplate("admin/" + t + ".html")
}
func (c Admin) GetView(view string) revel.Result {
return c.RenderTemplate("admin/" + view)
}
+114
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.RenderArgs["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
}
@@ -0,0 +1,235 @@
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.RenderArgs["recommendTags"] = strings.Join(recommendTags, ",")
c.RenderArgs["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.RenderArgs["demoUsername"] = configService.GetGlobalStringConfig("demoUsername")
c.RenderArgs["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.RenderArgs["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 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)
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.RenderArgs["emailsNl"] = emailsNl
c.RenderArgs["str"] = configService.GlobalStringConfigs
c.RenderArgs["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.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["emails"] = emails
c.RenderArgs["keywords"] = keywords
return c.RenderTemplate("admin/email/list.html")
}
@@ -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.RenderArgs["recommendTags"] = strings.Join(recommendTags, ",")
c.RenderArgs["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.RenderArgs["demoUsername"] = configService.GetGlobalStringConfig("demoUsername")
c.RenderArgs["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.RenderArgs["str"] = configService.GlobalStringConfigs
c.RenderArgs["arr"] = configService.GlobalArrayConfigs
c.RenderArgs["noteSubDomain"] = configService.GetGlobalStringConfig("noteSubDomain")
c.RenderArgs["blogSubDomain"] = configService.GetGlobalStringConfig("blogSubDomain")
c.RenderArgs["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)
}
@@ -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)
}
@@ -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 userPageSize == 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.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["users"] = users
c.RenderArgs["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.RenderArgs["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)
}
+132
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{})
}
+465
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
}
```
+47
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就是这样, 历史原因)
+69
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)
}
+189
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
}
+163
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
}
+618
View File
@@ -0,0 +1,618 @@
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"
// "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 != "" {
noteOrContent.Content = strings.Replace(noteOrContent.Content, "fileId="+file.LocalFileId, "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)
}
// 如果传了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
}
@@ -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)
}
+56
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)
}
+154
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
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
}
+64 -34
View File
@@ -1,20 +1,19 @@
package controllers package controllers
import ( import (
"github.com/leanote/leanote/app/service"
"github.com/leanote/leanote/app/info" "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" "github.com/revel/revel"
"strings" "strings"
) )
// 该文件初始化所有service方法
var userService *service.UserService var userService *service.UserService
var noteService *service.NoteService var noteService *service.NoteService
var trashService *service.TrashService var trashService *service.TrashService
var notebookService *service.NotebookService var notebookService *service.NotebookService
var noteContentHistoryService *service.NoteContentHistoryService var noteContentHistoryService *service.NoteContentHistoryService
var authService *service.AuthService var authService *service.AuthService
var shareService *service.ShareService var shareService *service.ShareService
var blogService *service.BlogService var blogService *service.BlogService
@@ -22,12 +21,17 @@ var tagService *service.TagService
var pwdService *service.PwdService var pwdService *service.PwdService
var tokenService *service.TokenService var tokenService *service.TokenService
var suggestionService *service.SuggestionService 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 pageSize = 1000
var defaultSortField = "UpdatedTime" var defaultSortField = "UpdatedTime"
var leanoteUserId = "52d26b4e99c37b609a000001"
var siteUrl = "http://leanote.com"
var openRegister = true
// 拦截器 // 拦截器
// 不需要拦截的url // 不需要拦截的url
@@ -43,29 +47,41 @@ var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": tru
"FindPassword2": true, "FindPassword2": true,
"FindPasswordUpdate": true, "FindPasswordUpdate": true,
"Suggestion": true, "Suggestion": true,
}, },
"Note": map[string]bool{"ToPdf": true},
"Blog": map[string]bool{"Index": true, "Blog": map[string]bool{"Index": true,
"View": true, "View": true,
"AboutMe": true, "AboutMe": true,
"SearchBlog": 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, "User": map[string]bool{"UpdateEmail": true,
"ActiveEmail":true, "ActiveEmail": true,
}, },
"oauth": map[string]bool{"githubCallback": 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 { func needValidate(controller, method string) bool {
// 在里面 // 在里面
if v, ok := commonUrl[controller]; ok { if v, ok := commonUrl[controller]; ok {
// 不在commonUrl里 // 在commonUrl里
if _, ok2 := v[method]; ok2 { if _, ok2 := v[method]; ok2 {
return false return false
} }
return true return true
} else { } else {
// controller不在这里的, 肯定要验证 // controller不在这里的, 肯定要验证
return true; return true
} }
} }
func AuthInterceptor(c *revel.Controller) revel.Result { func AuthInterceptor(c *revel.Controller) revel.Result {
@@ -73,13 +89,11 @@ func AuthInterceptor(c *revel.Controller) revel.Result {
var controller = strings.Title(c.Name) var controller = strings.Title(c.Name)
var method = strings.Title(c.MethodName) var method = strings.Title(c.MethodName)
// 是否需要验证? // 是否需要验证?
if !needValidate(controller, method) { if !needValidate(controller, method) {
return nil return nil
} }
// 验证是否已登录 // 验证是否已登录
if userId, ok := c.Session["UserId"]; ok && userId != "" { if userId, ok := c.Session["UserId"]; ok && userId != "" {
return nil // 已登录 return nil // 已登录
@@ -95,6 +109,36 @@ func AuthInterceptor(c *revel.Controller) revel.Result {
return c.Redirect("/login") 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() { func init() {
// interceptor // interceptor
// revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Index{}) // Index.Note自己校验 // revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Index{}) // Index.Note自己校验
@@ -102,28 +146,14 @@ func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Note{}) revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Note{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Share{}) revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Share{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &User{}) revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &User{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Album{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &File{}) 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{}) 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() { revel.OnAppStart(func() {
leanoteUserId, _ = revel.Config.String("adminUsername") // 博客初始化模板
siteUrl, _ = revel.Config.String("site.url") blog.Init()
openRegister, _ = revel.Config.Bool("register.open")
}) })
} }
@@ -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.RenderArgs["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)
}
}
@@ -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.RenderArgs["userInfo"] = userInfo
// 得到博客设置信息
c.RenderArgs["allowCustomDomain"] = configService.GetGlobalStringConfig("allowCustomDomain")
userBlog := blogService.GetUserBlog(userId)
c.RenderArgs["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.RenderArgs["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.RenderArgs["userInfo"] = userInfo
c.RenderArgs["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.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["blogs"] = blogs
c.RenderArgs["keywords"] = keywords
userAndBlog := userService.GetUserAndBlog(c.GetUserId())
c.RenderArgs["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.RenderArgs["title"] = c.Message("Update Post Abstract")
note := noteService.GetNoteAndContent(noteId, c.GetUserId())
if !note.Note.IsBlog {
return c.E404()
}
c.RenderArgs["note"] = note
c.RenderArgs["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.RenderArgs["title"] = c.Message("Blog Base Info")
return c.RenderTemplate("member/blog/base.html")
}
func (c MemberBlog) Comment() revel.Result {
c.common()
c.RenderArgs["title"] = c.Message("Comment")
return c.RenderTemplate("member/blog/comment.html")
}
func (c MemberBlog) Paging() revel.Result {
c.common()
c.RenderArgs["title"] = c.Message("Paging")
return c.RenderTemplate("member/blog/paging.html")
}
func (c MemberBlog) Cate() revel.Result {
userBlog := c.common()
c.RenderArgs["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.RenderArgs["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.RenderArgs["title"] = c.Message("Add Single")
c.RenderArgs["singleId"] = singleId
if singleId != "" {
c.RenderArgs["title"] = c.Message("Update Single")
c.RenderArgs["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.RenderArgs["title"] = c.Message("Single")
c.RenderArgs["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.RenderArgs["activeTheme"] = activeTheme
c.RenderArgs["otherThemes"] = otherThemes
c.RenderArgs["optionThemes"] = themeService.GetDefaultThemes()
c.RenderArgs["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.RenderArgs["title"] = c.Message("Update Theme")
c.RenderArgs["isNew"] = isNew
// 先复制之
c.RenderArgs["themeId"] = themeId
// 得到脚本目录
userId := c.GetUserId()
theme := themeService.GetTheme(userId, themeId)
if theme.ThemeId == "" {
return c.E404()
}
c.RenderArgs["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.RenderArgs["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.RenderArgs["fileUrlPath"] = re.Id
c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["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)
}
@@ -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.RenderArgs["title"] = c.Message("My Group")
c.RenderArgs["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)
}
@@ -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.RenderArgs["title"] = c.Message("Leanote Member Center")
c.RenderArgs["countNote"] = noteService.CountNote(c.GetUserId())
c.RenderArgs["countBlog"] = noteService.CountBlog(c.GetUserId())
c.SetLocale()
return c.RenderTemplate("member/index.html")
}
// 模板
func (c MemberIndex) T(t string) revel.Result {
c.RenderArgs["str"] = configService.GlobalStringConfigs
c.RenderArgs["arr"] = configService.GlobalArrayConfigs
c.RenderArgs["map"] = configService.GlobalMapConfigs
c.RenderArgs["arrMap"] = configService.GlobalArrMapConfigs
return c.RenderTemplate("admin/" + t + ".html")
}
func (c MemberIndex) GetView(view string) revel.Result {
return c.RenderTemplate("admin/" + view)
}
@@ -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.RenderArgs["title"] = c.Message("Username")
return c.RenderTemplate("member/user/username.html")
}
func (c MemberUser) Email() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = c.Message("Email")
return c.RenderTemplate("member/user/email.html")
}
func (c MemberUser) Password() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = c.Message("Password")
return c.RenderTemplate("member/user/password.html")
}
func (c MemberUser) Avatar() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = c.Message("Avatar")
c.RenderArgs["globalConfigs"] = configService.GetGlobalConfigForUser()
return c.RenderTemplate("member/user/avatar.html")
}
+135
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() {
})
}
+321 -17
View File
@@ -2,9 +2,11 @@ package db
import ( import (
"fmt" "fmt"
"labix.org/v2/mgo" . "github.com/leanote/leanote/app/lea"
"labix.org/v2/mgo/bson"
"github.com/revel/revel" "github.com/revel/revel"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"strings"
) )
// Init mgo and the common DAO // Init mgo and the common DAO
@@ -23,9 +25,12 @@ var ShareNotebooks *mgo.Collection
var HasShareNotes *mgo.Collection var HasShareNotes *mgo.Collection
var Blogs *mgo.Collection var Blogs *mgo.Collection
var Users *mgo.Collection var Users *mgo.Collection
var Groups *mgo.Collection
var GroupUsers *mgo.Collection
var Tags *mgo.Collection var Tags *mgo.Collection
var TagNotes *mgo.Collection var NoteTags *mgo.Collection
var TagCounts *mgo.Collection
var UserBlogs *mgo.Collection var UserBlogs *mgo.Collection
@@ -33,13 +38,51 @@ var Tokens *mgo.Collection
var Suggestions *mgo.Collection 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() { func Init(url, dbname string) {
var url string ok := true
var ok bool config := revel.Config
config := revel.Config; if url == "" {
url, ok = config.String("db.url") url, ok = config.String("db.url")
dbname, _ := config.String("db.dbname") 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 { if !ok {
host, _ := revel.Config.String("db.host") host, _ := revel.Config.String("db.host")
port, _ := revel.Config.String("db.port") port, _ := revel.Config.String("db.port")
@@ -51,6 +94,7 @@ func Init() {
} }
url = "mongodb://" + usernameAndPassword + host + ":" + port + "/" + dbname url = "mongodb://" + usernameAndPassword + host + ":" + port + "/" + dbname
} }
Log(url)
// [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options] // [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
// mongodb://myuser:mypass@localhost:40001,otherhost:40001/mydb // mongodb://myuser:mypass@localhost:40001,otherhost:40001/mydb
@@ -80,28 +124,258 @@ func Init() {
// user // user
Users = Session.DB(dbname).C("users") Users = Session.DB(dbname).C("users")
// group
Groups = Session.DB(dbname).C("groups")
GroupUsers = Session.DB(dbname).C("group_users")
// blog // blog
Blogs = Session.DB(dbname).C("blogs") Blogs = Session.DB(dbname).C("blogs")
// tag // tag
Tags = Session.DB(dbname).C("tags") 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 // blog
UserBlogs = Session.DB(dbname).C("user_blogs") UserBlogs = Session.DB(dbname).C("user_blogs")
BlogSingles = Session.DB(dbname).C("blog_singles")
Themes = Session.DB(dbname).C("themes")
// find password // find password
Tokens = Session.DB(dbname).C("tokens") Tokens = Session.DB(dbname).C("tokens")
// // Suggestion
Suggestions = Session.DB(dbname).C("suggestions") 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")
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")
collectionNames, err := Session.DB(dbname).CollectionNames()
// 如果没有表, 则表示是第一次安装
if (err != nil || len(collectionNames) == 0) {
Log("init db index")
initIndex(dbname)
}
} }
func init() { func initIndex(dbname string) {
revel.OnAppStart(func() { // 索引
Init() indexes := map[string][]mgo.Index{
}) "notebooks": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"notes": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
mgo.Index{
Key: []string{"IsTrash"},
Background: true,
},
mgo.Index{
Key: []string{"UrlTitle"},
Background: true,
},
mgo.Index{
Key: []string{"IsBlog"},
Background: true,
},
mgo.Index{
Key: []string{"CreatedTime"},
Background: true,
},
mgo.Index{
Key: []string{"UpdatedTime"},
Background: true,
},
mgo.Index{
Key: []string{"PublicTime"},
Background: true,
},
mgo.Index{
Key: []string{"RecommendTime"},
Background: true,
},
mgo.Index{
Key: []string{"NotebookId"},
Background: true,
},
mgo.Index{
Key: []string{"UserId", "IsBlog"},
Background: true,
},
},
"note_contents": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"note_content_histories": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"share_notes": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
mgo.Index{
Key: []string{"ToUserId"},
Background: true,
},
},
"share_notebooks": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
mgo.Index{
Key: []string{"ToUserId"},
Background: true,
},
},
"has_share_notes": []mgo.Index{
mgo.Index{
Key: []string{"UserId", "ToUserId"},
Background: true,
Unique: true,
},
},
"users": []mgo.Index{
mgo.Index{
Key: []string{"Email"},
Background: true,
},
mgo.Index{
Key: []string{"Username"},
Background: true,
},
},
"groups": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"group_users": []mgo.Index{
mgo.Index{
Key: []string{"GroupId"},
Background: true,
},
},
"tags": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"note_tags": []mgo.Index{
mgo.Index{
Key: []string{"UserId", "TagId"},
Background: true,
},
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"tag_count": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"blog_singles": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"blog_likes": []mgo.Index{
mgo.Index{
Key: []string{"NoteId"},
Background: true,
},
},
"blog_comments": []mgo.Index{
mgo.Index{
Key: []string{"NoteId"},
Background: true,
},
},
"themes": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"note_images": []mgo.Index{
mgo.Index{
Key: []string{"ImageId"},
Background: true,
},
mgo.Index{
Key: []string{"NoteId"},
Background: true,
},
},
"albums": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"files": []mgo.Index{
mgo.Index{
Key: []string{"UserId"},
Background: true,
},
},
"attachs": []mgo.Index{
mgo.Index{
Key: []string{"NoteId"},
Background: true,
},
},
"sessions": []mgo.Index{
mgo.Index{
Key: []string{"SessionId"},
Background: true,
},
},
}
for collection, ins := range indexes {
col := Session.DB(dbname).C(collection)
for _, in := range ins {
col.EnsureIndex(in)
}
}
} }
func close() { func close() {
@@ -143,22 +417,29 @@ func UpdateByIdAndUserId2(collection *mgo.Collection, id, userId bson.ObjectId,
return Err(err) return Err(err)
} }
func UpdateByIdAndUserIdField(collection *mgo.Collection, id, userId, field string, value interface{}) bool { 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 { func UpdateByIdAndUserIdMap(collection *mgo.Collection, id, userId string, v bson.M) bool {
return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": v}) return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": v})
} }
func UpdateByIdAndUserIdField2(collection *mgo.Collection, id, userId bson.ObjectId, field string, value interface{}) bool { 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 { func UpdateByIdAndUserIdMap2(collection *mgo.Collection, id, userId bson.ObjectId, v bson.M) bool {
return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": v}) return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": v})
} }
// //
func UpdateByQField(collection *mgo.Collection, q interface{}, field string, value interface{}) bool { func UpdateByQField(collection *mgo.Collection, q interface{}, field string, value interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": bson.M{field: value}}) _, err := collection.UpdateAll(q, bson.M{"$set": bson.M{field: value}})
return Err(err) 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 { func UpdateByQMap(collection *mgo.Collection, q interface{}, v interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": v}) _, err := collection.UpdateAll(q, bson.M{"$set": v})
@@ -224,6 +505,7 @@ func GetByQWithFields(collection *mgo.Collection, q bson.M, fields []string, i i
} }
collection.Find(q).Select(selector).One(i) collection.Find(q).Select(selector).One(i)
} }
// 查询某些字段, q是查询条件, fields是字段名列表 // 查询某些字段, q是查询条件, fields是字段名列表
func ListByQWithFields(collection *mgo.Collection, q bson.M, fields []string, i interface{}) { func ListByQWithFields(collection *mgo.Collection, q bson.M, fields []string, i interface{}) {
selector := make(bson.M, len(fields)) selector := make(bson.M, len(fields))
@@ -239,6 +521,11 @@ func GetByIdAndUserId2(collection *mgo.Collection, id, userId bson.ObjectId, i i
collection.Find(GetIdAndUserIdBsonQ(id, userId)).One(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 { func Count(collection *mgo.Collection, q interface{}) int {
@@ -272,9 +559,26 @@ func Err(err error) bool {
fmt.Println(err) fmt.Println(err)
// 删除时, 查找 // 删除时, 查找
if err.Error() == "not found" { if err.Error() == "not found" {
return true; return true
} }
return false return false
} }
return true 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("重连失败!!!! 警告")
}
}
}
-72
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/leanote/messages/"
var targetBasePath = "/Users/life/Documents/Go/package/src/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")
}
+15
View File
@@ -0,0 +1,15 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
type Album struct {
AlbumId bson.ObjectId `bson:"_id,omitempty"` //
UserId bson.ObjectId `bson:"UserId"`
Name string `Name` // album name
Type int `Type` // type, the default is image: 0
Seq int `Seq`
CreatedTime time.Time `CreatedTime`
}
+119
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
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
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
}
+88 -3
View File
@@ -1,31 +1,36 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
"time"
) )
// 只为blog, 不为note // 只为blog, 不为note
type BlogItem struct { type BlogItem struct {
Note Note
Abstract string
Content string // 可能是content的一部分, 截取. 点击more后就是整个信息了 Content string // 可能是content的一部分, 截取. 点击more后就是整个信息了
HasMore bool // 是否是否还有 HasMore bool // 是否是否还有
User User // 用户信息
} }
type UserBlogBase struct { type UserBlogBase struct {
Logo string `Logo` Logo string `Logo`
Title string `Title` // 标题 Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题 SubTitle string `SubTitle` // 副标题
AboutMe string `AboutMe` // 关于我 // AboutMe string `AboutMe` // 关于我
} }
type UserBlogComment struct { type UserBlogComment struct {
CanComment bool `CanComment` // 是否可以评论 CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId` DisqusId string `DisqusId`
} }
type UserBlogStyle struct { type UserBlogStyle struct {
Style string `Style` // 风格 Style string `Style` // 风格
Css string `Css` // 自定义css
} }
// 每个用户一份博客设置信息 // 每个用户一份博客设置信息
@@ -34,10 +39,90 @@ type UserBlog struct {
Logo string `Logo` Logo string `Logo`
Title string `Title` // 标题 Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题 SubTitle string `SubTitle` // 副标题
AboutMe string `AboutMe` // 关于我 AboutMe string `AboutMe` // 关于我, 弃用
CanComment bool `CanComment` // 是否可以评论 CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId` DisqusId string `DisqusId`
Style string `Style` // 风格 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
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
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`
}
+21
View File
@@ -0,0 +1,21 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
type File struct {
FileId bson.ObjectId `bson:"_id,omitempty"` //
UserId bson.ObjectId `bson:"UserId"`
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, "" = 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
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
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
}
+40 -1
View File
@@ -1,7 +1,7 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
"time" "time"
) )
@@ -17,16 +17,34 @@ type Note struct {
ImgSrc string `ImgSrc` // 图片, 第一张缩略图地址 ImgSrc string `ImgSrc` // 图片, 第一张缩略图地址
Tags []string `Tags,omitempty` Tags []string `Tags,omitempty`
IsTrash bool `IsTrash` // 是否是trash, 默认是false IsTrash bool `IsTrash` // 是否是trash, 默认是false
IsBlog bool `IsBlog,omitempty` // 是否设置成了blog 2013/12/29 新加 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是否置顶 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 IsMarkdown bool `IsMarkdown` // 是否是markdown笔记, 默认是false
AttachNum int `AttachNum` // 2014/9/21, attachments num
CreatedTime time.Time `CreatedTime` CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime` UpdatedTime time.Time `UpdatedTime`
RecommendTime time.Time `RecommendTime,omitempty` // 推荐时间
PublicTime time.Time `PublicTime,omitempty` // 发表时间, 公开为博客则设置
UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了 UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了
// 2015/1/15, 更新序号
Usn int `Usn` // UpdateSequenceNum
IsDeleted bool `IsDeleted` // 删除位
} }
// 内容 // 内容
@@ -62,3 +80,24 @@ type NoteContentHistory struct {
UserId bson.ObjectId `bson:"UserId"` // 所属者 UserId bson.ObjectId `bson:"UserId"` // 所属者
Histories []EachHistory `Histories` Histories []EachHistory `Histories`
} }
// 为了NoteController接收参数
// 更新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
}
+8 -3
View File
@@ -1,7 +1,7 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
"time" "time"
) )
@@ -13,15 +13,20 @@ type Notebook struct {
ParentNotebookId bson.ObjectId `bson:"ParentNotebookId,omitempty"` // 上级 ParentNotebookId bson.ObjectId `bson:"ParentNotebookId,omitempty"` // 上级
Seq int `Seq` // 排序 Seq int `Seq` // 排序
Title string `Title` // 标题 Title string `Title` // 标题
UrlTitle string `UrlTitle` // Url标题 2014/11.11加
NumberNotes int `NumberNotes` // 笔记数 NumberNotes int `NumberNotes` // 笔记数
IsTrash bool `IsTrash,omitempty` // 是否是trash, 默认是false IsTrash bool `IsTrash,omitempty` // 是否是trash, 默认是false
IsBlog bool `IsBlog,omitempty` // 是否是Blog 2013/12/29 新加 IsBlog bool `IsBlog,omitempty` // 是否是Blog 2013/12/29 新加
CreatedTime time.Time `CreatedTime,omitempty` CreatedTime time.Time `CreatedTime,omitempty`
UpdatedTime time.Time `UpdatedTime,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 { type Notebooks struct {
Notebook Notebook
Subs SubNotebooks // 子notebook 在数据库中是没有的 Subs SubNotebooks // 子notebook 在数据库中是没有的
@@ -32,7 +37,7 @@ func (this SubNotebooks) Len() int {
return len(this) return len(this)
} }
func (this SubNotebooks) Less(i, j int) bool { 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) { func (this SubNotebooks) Swap(i, j int) {
this[i], this[j] = this[j], this[i] this[i], this[j] = this[j], this[i]
+1 -2
View File
@@ -1,7 +1,6 @@
package info package info
import ( import ()
)
// controller ajax返回 // controller ajax返回
type Re struct { type Re struct {
+19
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
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这个时间会自动清空
}
+7 -3
View File
@@ -1,7 +1,7 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
"time" "time"
) )
@@ -76,7 +76,9 @@ type SharingNotebookAndNotes struct {
type ShareNotebook struct { type ShareNotebook struct {
ShareNotebookId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键 ShareNotebookId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"` 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"` NotebookId bson.ObjectId `bson:"NotebookId"`
Seq int `bson:"Seq"` // 排序 Seq int `bson:"Seq"` // 排序
Perm int `bson:"Perm"` // 权限, 其下所有notes 0只读, 1可修改 Perm int `bson:"Perm"` // 权限, 其下所有notes 0只读, 1可修改
@@ -134,7 +136,9 @@ type ShareNotebooksByUser struct {
type ShareNote struct { type ShareNote struct {
ShareNoteId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键 ShareNoteId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"` 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"` NoteId bson.ObjectId `bson:"NoteId"`
Perm int `bson:"Perm"` // 权限, 0只读, 1可修改 Perm int `bson:"Perm"` // 权限, 0只读, 1可修改
CreatedTime time.Time `CreatedTime` CreatedTime time.Time `CreatedTime`
+1 -1
View File
@@ -1,7 +1,7 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
) )
// 建议 // 建议
+38 -1
View File
@@ -1,19 +1,56 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
"time"
) )
// 这里主要是为了统计每个tag的note数目 // 这里主要是为了统计每个tag的note数目
// 暂时没用
/*
type TagNote struct { type TagNote struct {
TagId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键 TagId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"` UserId bson.ObjectId `bson:"UserId"`
Tag string `Title` // 标题 Tag string `Title` // 标题
NoteNum int `NoteNum` // note数目 NoteNum int `NoteNum` // note数目
} }
*/
// 每个用户一条记录, 存储用户的所有tags // 每个用户一条记录, 存储用户的所有tags
type Tag struct { type Tag struct {
UserId bson.ObjectId `bson:"_id"` UserId bson.ObjectId `bson:"_id"`
Tags []string `Tags` 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
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`
}
+1 -1
View File
@@ -1,7 +1,7 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
"time" "time"
) )
+57 -1
View File
@@ -1,7 +1,7 @@
package info package info
import ( import (
"labix.org/v2/mgo/bson" "gopkg.in/mgo.v2/bson"
"time" "time"
) )
@@ -20,16 +20,72 @@ type User struct {
Pwd string `bson:"Pwd" json:"-"` Pwd string `bson:"Pwd" json:"-"`
CreatedTime time.Time `CreatedTime` CreatedTime time.Time `CreatedTime`
Logo string `Logo` // 9-24
// 主题 // 主题
Theme string `Theme` Theme string `Theme`
// 用户配置 // 用户配置
NotebookWidth int `NotebookWidth` // 笔记本宽度 NotebookWidth int `NotebookWidth` // 笔记本宽度
NoteListWidth int `NoteListWidth` // 笔记列表宽度 NoteListWidth int `NoteListWidth` // 笔记列表宽度
MdEditorWidth int `MdEditorWidth` // markdown 左侧编辑器宽度
LeftIsMin bool `LeftIsMin` // 左侧是否是隐藏的, 默认是打开的 LeftIsMin bool `LeftIsMin` // 左侧是否是隐藏的, 默认是打开的
// 这里 第三方登录 // 这里 第三方登录
ThirdUserId string `ThirdUserId` // 用户Id, 在第三方中唯一可识别 ThirdUserId string `ThirdUserId` // 用户Id, 在第三方中唯一可识别
ThirdUsername string `ThirdUsername` // 第三方中username, 为了显示 ThirdUsername string `ThirdUsername` // 第三方中username, 为了显示
ThirdType int `ThirdType` // 第三方类型 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 // 各个页面
} }
+11 -1
View File
@@ -1,12 +1,22 @@
package info package info
import ( import (
"math"
) )
// 分页数据 // 分页数据
type Page struct { type Page struct {
CurPage int // 当前页码 CurPage int // 当前页码
TotalPage int // 总页 TotalPage int // 总页
PerPageSize int
Count int // 总记录数
List interface{} 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}
}
+334 -17
View File
@@ -1,14 +1,25 @@
package app package app
import ( 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"
_ "github.com/leanote/leanote/app/lea/binder" _ "github.com/leanote/leanote/app/lea/binder"
"reflect" "github.com/leanote/leanote/app/lea/i18n"
"fmt" "github.com/leanote/leanote/app/lea/route"
"github.com/leanote/leanote/app/service"
"github.com/revel/revel"
"html/template" "html/template"
"math" "math"
"net/url"
"reflect"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -16,17 +27,22 @@ func init() {
// Filters is the default set of global filters. // Filters is the default set of global filters.
revel.Filters = []revel.Filter{ revel.Filters = []revel.Filter{
revel.PanicFilter, // Recover from panics and display an error page instead. revel.PanicFilter, // Recover from panics and display an error page instead.
revel.RouterFilter, // Use the routing table to select the right Action route.RouterFilter,
// revel.RouterFilter, // Use the routing table to select the right Action
// AuthFilter, // Invoke the action. // AuthFilter, // Invoke the action.
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters. revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
revel.ParamsFilter, // Parse parameters into Controller.Params. revel.ParamsFilter, // Parse parameters into Controller.Params.
revel.SessionFilter, // Restore and write the session cookie. revel.SessionFilter, // Restore and write the session cookie.
// session.SessionFilter, // leanote memcache session life // 使用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.FlashFilter, // Restore and write the flash cookie.
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie. revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
revel.I18nFilter, // Resolve the requested language // revel.I18nFilter, // Resolve the requested language
i18n.I18nFilter, // Resolve the requested language by leanote
revel.InterceptorFilter, // Run interceptors around the action. revel.InterceptorFilter, // Run interceptors around the action.
revel.CompressFilter, // Compress the result. revel.CompressFilter, // Compress the result.
revel.ActionInvoker, // Invoke the action. revel.ActionInvoker, // Invoke the action.
@@ -35,16 +51,70 @@ func init() {
revel.TemplateFuncs["raw"] = func(str string) template.HTML { revel.TemplateFuncs["raw"] = func(str string) template.HTML {
return template.HTML(str) return template.HTML(str)
} }
revel.TemplateFuncs["add"] = func(i int) template.HTML { revel.TemplateFuncs["trim"] = func(str string) string {
i = i + 1; str = strings.Trim(str, " ")
return template.HTML(fmt.Sprintf("%v", i)) 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 { revel.TemplateFuncs["concat"] = func(s1, s2 string) template.HTML {
return template.HTML(s1 + s2) 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 { revel.TemplateFuncs["datetime"] = func(t time.Time) template.HTML {
return template.HTML(t.Format("2006-01-02 15:04:05")) 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是否有该字段 // interface是否有该字段
revel.TemplateFuncs["has"] = func(i interface{}, key string) bool { revel.TemplateFuncs["has"] = func(i interface{}, key string) bool {
@@ -54,6 +124,122 @@ func init() {
} }
// tags // tags
// 2014/12/30 标签添加链接
revel.TemplateFuncs["blogTags"] = func(renderArgs map[string]interface{}, tags []string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
}
locale, _ := renderArgs[revel.CurrentLocaleRenderArg].(string)
tagStr := ""
lenTags := len(tags)
tagPostUrl, _ := renderArgs["tagPostsUrl"].(string)
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 + "/" + url.QueryEscape(tag)
tagStr += "<a class=\"" + classes + "\" href=\"" + url + "\">" + str + "</a>"
if i != lenTags-1 {
tagStr += " "
}
}
return template.HTML(tagStr)
}
revel.TemplateFuncs["blogTagsForExport"] = func(renderArgs map[string]interface{}, tags []string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
}
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.CurrentLocaleRenderArg].(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.CurrentLocaleRenderArg].(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.CurrentLocaleRenderArg].(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 + url.QueryEscape(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 { revel.TemplateFuncs["blogTags"] = func(tags []string) template.HTML {
if tags == nil || len(tags) == 0 { if tags == nil || len(tags) == 0 {
return "" return ""
@@ -74,13 +260,90 @@ func init() {
} }
return template.HTML(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.CurrentLocaleRenderArg].(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 // pagination
revel.TemplateFuncs["page"] = func(userId, notebookId string, page, pageSize, count int) template.HTML { revel.TemplateFuncs["page"] = func(urlBase string, page, pageSize, count int) template.HTML {
if count == 0 { if count == 0 {
return ""; return ""
} }
totalPage := int(math.Ceil(float64(count)/float64(pageSize))) totalPage := int(math.Ceil(float64(count) / float64(pageSize)))
preClass := "" preClass := ""
prePage := page - 1 prePage := page - 1
@@ -91,11 +354,6 @@ func init() {
nextPage := page + 1 nextPage := page + 1
var preUrl, nextUrl string 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) nextUrl = urlBase + "?page=" + strconv.Itoa(nextPage)
@@ -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>") 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 // init Email
revel.OnAppStart(func() { revel.OnAppStart(func() {
// 数据库
db.Init("", "")
// email配置
InitEmail() InitEmail()
InitVd()
// memcache.InitMemcache() // session服务
// 其它service
service.InitService()
controllers.InitService()
admin.InitService()
member.InitService()
service.ConfigS.InitGlobalConfigs()
api.InitService()
}) })
} }
+11
View File
@@ -2,6 +2,7 @@ package lea
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/revel/revel" "github.com/revel/revel"
) )
@@ -13,3 +14,13 @@ func LogJ(i interface{}) {
b, _ := json.MarshalIndent(i, "", " ") b, _ := json.MarshalIndent(i, "", " ")
revel.INFO.Println(string(b)) 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))
}
+17 -15
View File
@@ -1,10 +1,11 @@
package lea package lea
import ( import (
"github.com/revel/revel"
"net/smtp" "net/smtp"
"strings" "strings"
"github.com/revel/revel"
) )
// 发送邮件 // 发送邮件
var host = "smtp.ym.163.com" var host = "smtp.ym.163.com"
var port = "25" var port = "25"
@@ -12,7 +13,7 @@ var username = "noreply@leanote.com"
var password = "---" var password = "---"
func InitEmail() { func InitEmail() {
config := revel.Config; config := revel.Config
host, _ = config.String("email.host") host, _ = config.String("email.host")
port, _ = config.String("email.port") port, _ = config.String("email.port")
username, _ = config.String("email.username") username, _ = config.String("email.username")
@@ -22,14 +23,14 @@ func InitEmail() {
var bodyTpl = ` var bodyTpl = `
<html> <html>
<body> <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> <div>
<div style="float:left; height: 40px;"> <div style="float:left; height: 40px;">
<a href="http://leanote.com" style="font-size: 24px">leanote</a> <a href="http://leanote.com" style="font-size: 24px">leanote</a>
</div> </div>
<div style="float:left; height:40px; line-height:16px;"> <div style="float:left; height:40px; line-height:40px;">
&nbsp;&nbsp;| &nbsp;<span style="font-size:24px">$title</span> &nbsp;&nbsp;| &nbsp;<span style="font-size:14px">$title</span>
</div> </div>
<div style="clear:both"></div> <div style="clear:both"></div>
</div> </div>
@@ -50,13 +51,14 @@ var bodyTpl = `
font-size: 12px; font-size: 12px;
} }
</style> </style>
<a href="http://leanote.com">leanote</a>, your own cloud note <a href="http://leanote.com">leanote</a>, your own cloud note!
</div> </div>
</div> </div>
</body> </body>
</html> </html>
` `
func SendEmail(to, subject, title, body string) bool {
func SendEmailOld(to, subject, body string) bool {
hp := strings.Split(host, ":") hp := strings.Split(host, ":")
auth := smtp.PlainAuth("", username, password, hp[0]) auth := smtp.PlainAuth("", username, password, hp[0])
@@ -64,26 +66,26 @@ func SendEmail(to, subject, title, body string) bool {
mailtype := "html" mailtype := "html"
if mailtype == "html" { if mailtype == "html" {
content_type = "Content-Type: text/"+ mailtype + "; charset=UTF-8" content_type = "Content-Type: text/" + mailtype + "; charset=UTF-8"
} else{ } else {
content_type = "Content-Type: text/plain" + "; charset=UTF-8" content_type = "Content-Type: text/plain" + "; charset=UTF-8"
} }
// 登录之 //body = strings.Replace(bodyTpl, "$body", body, 1)
body = strings.Replace(bodyTpl, "$body", body, 1) //body = strings.Replace(body, "$title", title, 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) 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, ";") send_to := strings.Split(to, ";")
err := smtp.SendMail(host+":"+port, auth, username, send_to, msg) err := smtp.SendMail(host+":"+port, auth, username, send_to, msg)
if err != nil { if err != nil {
Log(err)
return false return false
} }
return true return true
} }
func SendToLeanote(subject, title, body string) { func SendToLeanoteOld(subject, title, body string) {
to := "leanote@leanote.com" to := "leanote@leanote.com"
SendEmail(to, subject, title, body); SendEmailOld(to, subject, body)
} }
+148 -3
View File
@@ -1,9 +1,11 @@
package lea package lea
import ( import (
"strings" "io"
"path/filepath" "io/ioutil"
"os" "os"
"path/filepath"
"strings"
) )
// 分离文件名与扩展名(包含.) // 分离文件名与扩展名(包含.)
@@ -13,7 +15,7 @@ func SplitFilename(filename string) (baseName, ext string) {
ext = SubstringByte(filename, strings.LastIndex(filename, ".")) ext = SubstringByte(filename, strings.LastIndex(filename, "."))
baseName = strings.TrimRight(filename, ext) baseName = strings.TrimRight(filename, ext)
ext = strings.ToLower(ext) ext = strings.ToLower(ext)
return; return
} }
// 转换文件的格式 // 转换文件的格式
@@ -30,6 +32,16 @@ func GetFilename(path string) string {
return filepath.Base(path) return filepath.Base(path)
} }
// file size
// length in bytes
func GetFilesize(path string) int64 {
fileinfo, err := os.Stat(path)
if err == nil {
return fileinfo.Size()
}
return 0
}
// 清空dir下所有的文件和文件夹 // 清空dir下所有的文件和文件夹
// RemoveAll会清空本文件夹, 所以还要创建之 // RemoveAll会清空本文件夹, 所以还要创建之
func ClearDir(dir string) bool { func ClearDir(dir string) bool {
@@ -43,3 +55,136 @@ func ClearDir(dir string) bool {
} }
return true 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)
if err != nil {
return nil
}
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
}
+22
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)
}
+192 -28
View File
@@ -1,16 +1,19 @@
package lea package lea
import ( import (
"fmt" "bytes"
"regexp"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt"
"github.com/PuerkitoBio/goquery"
"gopkg.in/mgo.v2/bson"
"io" "io"
"labix.org/v2/mgo/bson"
"time"
math_rand "math/rand" math_rand "math/rand"
"regexp"
"strings"
"time"
) )
// 字符串 // 字符串
@@ -22,6 +25,22 @@ func Md5(s string) string {
return hex.EncodeToString(h.Sum(nil)) 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 // Guid
func NewGuid() string { func NewGuid() string {
b := make([]byte, 48) b := make([]byte, 48)
@@ -122,39 +141,49 @@ func ReplaceAll(oldStr, pattern, newStr string) string {
return string(s) return string(s)
} }
func SubStringHTML(param string, length int, end string) string { // 获取纯文本
// 先取出<pre></pre>占位.. func SubStringHTMLToRaw(param string, length int) string {
if param == "" {
result := "" return param
}
// 1
n := 0 n := 0
var temp rune // 中文问题, 用rune来解决 var temp rune // 中文问题, 用rune来解决
isCode := false //是不是HTML代码
isHTML := false //是不是HTML特殊字符,如&nbsp;
rStr := []rune(param) 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] temp = rStr[i]
if temp == '<' { if temp == '<' {
isCode = true isCode = true
} else if temp == '&' { continue
isHTML = true } else if temp == '>' {
} else if temp == '>' && isCode {
n = n - 1
isCode = false isCode = false
} else if temp == ';' && isHTML { resultRune[n] = ' '
isHTML = false
n++
if n >= length {
break
} }
if !isCode && !isHTML { continue
n = n + 1
} }
result += string(temp) if !isCode {
resultRune[n] = temp
// s += string(temp)
n++
if n >= length { if n >= length {
break break
} }
} }
result += end }
result := string(resultRune[0:n])
return strings.Trim(result, " ")
}
// 自带方法补全html
func fixHtml(result string) string {
// 取出所有标签 // 取出所有标签
tempResult := ReplaceAll(result, "(>)[^<>]*(<?)", "$1$2") // 把标签中间的所有内容都去掉了 tempResult := ReplaceAll(result, "(>)[^<>]*(<?)", "$1$2") // 把标签中间的所有内容都去掉了
@@ -169,7 +198,7 @@ func SubStringHTML(param string, length int, end string) string {
p, _ := regexp.Compile("<(/?[a-zA-Z]+)[^<>]*>") // 得到所有的<div>, </div>... p, _ := regexp.Compile("<(/?[a-zA-Z]+)[^<>]*>") // 得到所有的<div>, </div>...
strs := p.FindAllString(tempResult, -1) strs := p.FindAllString(tempResult, -1)
// fmt.Println(strs) // fmt.Println(strs)
stack := make([]string, len(strs)) stack := make([]string, len(strs))
stackP := -1 stackP := -1
for _, each := range strs { for _, each := range strs {
@@ -196,6 +225,72 @@ func SubStringHTML(param string, length int, end string) string {
return result 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) { func IsGoodPwd(pwd string) (bool, string) {
if pwd == "" { if pwd == "" {
@@ -210,16 +305,16 @@ func IsGoodPwd(pwd string) (bool, string) {
// 是否是email // 是否是email
func IsEmail(email string) bool { func IsEmail(email string) bool {
if email == "" { 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 return ok
} }
// 是否只包含数字, 字母 -, _ // 是否只包含数字, 字母 -, _
func IsUsername(username string) bool { func IsUsername(username string) bool {
if username == "" { if username == "" {
return false; return false
} }
ok, _ := regexp.MatchString(`[^0-9a-zA-Z_\-]`, username) ok, _ := regexp.MatchString(`[^0-9a-zA-Z_\-]`, username)
return !ok return !ok
@@ -256,7 +351,7 @@ func RandomPwd(num int) string {
chars[j] = byte(i) chars[j] = byte(i)
j++ j++
} }
j--; j--
str := "" str := ""
math_rand.Seed(time.Now().UnixNano()) math_rand.Seed(time.Now().UnixNano())
@@ -267,3 +362,72 @@ func RandomPwd(num int) string {
return str 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
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
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)
}
+192
View File
@@ -0,0 +1,192 @@
package archive
import (
"archive/zip"
"fmt"
"io"
"os"
"path"
"strings"
)
// 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)
}
// 把首文件夹去掉, 即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
}
+43 -7
View File
@@ -1,9 +1,9 @@
package binder package binder
import ( import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/controllers" "github.com/revel/revel"
// "github.com/leanote/leanote/app/controllers/api"
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
@@ -53,16 +53,36 @@ func nextKey(key string) string {
} }
return key[:fieldLen] return key[:fieldLen]
} }
var leanoteStructBinder = revel.Binder{ var leanoteStructBinder = revel.Binder{
// name == "noteOrContent"
Bind: func(params *revel.Params, name string, typ reflect.Type) reflect.Value { 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) 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. // 这里, 如果没有点, 默认就是a.
// life // life
// fmt.Println("key:" + key); // files[0][LocalFileId]
// fmt.Println("name:" + name); // files[0][LocalFileId]
var suffix string var suffix string
var noPrefix = false 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 noPrefix = true
suffix = key suffix = key
// continue // continue
@@ -71,13 +91,18 @@ var leanoteStructBinder = revel.Binder{
// Strip off the prefix. e.g. foo.bar.baz => bar.baz // Strip off the prefix. e.g. foo.bar.baz => bar.baz
suffix = key[len(name)+1:] suffix = key[len(name)+1:]
} }
// fmt.Println(suffix);
fieldName := nextKey(suffix) // e.g. bar => "bar", bar.baz => "bar", bar[0] => "bar" fieldName := nextKey(suffix) // e.g. bar => "bar", bar.baz => "bar", bar[0] => "bar"
// fmt.Println(fieldName);
fieldLen := len(fieldName) fieldLen := len(fieldName)
if _, ok := fieldValues[fieldName]; !ok { if _, ok := fieldValues[fieldName]; !ok {
// Time to bind this field. Get it and make sure we can set it. // 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) fieldValue := result.FieldByName(fieldName)
// fmt.Println(fieldValue)
if !fieldValue.IsValid() { if !fieldValue.IsValid() {
continue continue
} }
@@ -86,9 +111,17 @@ var leanoteStructBinder = revel.Binder{
} }
var boundVal reflect.Value var boundVal reflect.Value
// 没有name前缀 // 没有name前缀
if(noPrefix) { if noPrefix {
// life
// fmt.Println("<<")
// fmt.Println(strings.Title(key[:fieldLen]));
boundVal = revel.Bind(params, key[:fieldLen], fieldValue.Type()) boundVal = revel.Bind(params, key[:fieldLen], fieldValue.Type())
} else { } 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()) boundVal = revel.Bind(params, key[:len(name)+1+fieldLen], fieldValue.Type())
} }
fieldValue.Set(boundVal) fieldValue.Set(boundVal)
@@ -116,5 +149,8 @@ func init() {
revel.TypeBinders[reflect.TypeOf(info.UserBlogComment{})] = leanoteStructBinder revel.TypeBinders[reflect.TypeOf(info.UserBlogComment{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(info.UserBlogStyle{})] = leanoteStructBinder revel.TypeBinders[reflect.TypeOf(info.UserBlogStyle{})] = leanoteStructBinder
revel.TypeBinders[reflect.TypeOf(info.Notebook{})] = 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
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
RenderArgs 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.RenderArgs)
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.RenderArgs, 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, // 为了显示错误
RenderArgs: 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, // 为了显示错误
RenderArgs: args,
CurBlogTpl: newBlogTplObject,
IsPreview: isPreview,
}
}
return r
}
////////////////////
//
type ErrorResult struct {
RenderArgs 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.RenderArgs == nil {
r.RenderArgs = make(map[string]interface{})
}
r.RenderArgs["Error"] = revelError
r.RenderArgs["Router"] = revel.MainRouter
// 不是preview就不要显示错误了
if r.IsPreview {
var b bytes.Buffer
out := io.Writer(resp.Out)
// out = ioutil.Discard
err = tmpl.Execute(&b, r.RenderArgs)
resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
b.WriteTo(out)
}
}
+401
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
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
}
+9
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
View File
@@ -0,0 +1,223 @@
package i18n
import (
"fmt"
"github.com/revel/revel"
"github.com/robfig/config"
"os"
"path/filepath"
"regexp"
"strings"
)
const (
CurrentLocaleRenderArg = "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.RenderArgs[CurrentLocaleRenderArg] = 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, ""
}
-26
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
}
-11
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")
}
+34 -24
View File
@@ -1,11 +1,13 @@
package netutil package netutil
import ( import (
"strings"
"os" "os"
// "path/filepath" "strings"
"net/http" // "path/filepath"
"io/ioutil"
. "github.com/leanote/leanote/app/lea" . "github.com/leanote/leanote/app/lea"
"io/ioutil"
"net"
"net/http"
) )
// net的util // net的util
@@ -13,29 +15,26 @@ import (
// toPath 文件保存的目录 // toPath 文件保存的目录
// 默认是/tmp // 默认是/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 == "" { if url == "" {
return; return
} }
content, err := GetContent(url) content, err := GetContent(url)
if err != nil { if err != nil {
return; return
} }
length = int64(len(content))
// a.html?a=a11&xxx // a.html?a=a11&xxx
url = trimQueryParams(url) url = trimQueryParams(url)
_, ext := SplitFilename(url) _, ext := SplitFilename(url)
if toPath == "" { if toPath == "" {
toPath = "/tmp" toPath = "/tmp"
} }
// dir := filepath.Dir(toPath) // dir := filepath.Dir(toPath)
newFilename := NewGuid() + ext newFilename = NewGuid() + ext
fullPath := toPath + "/" + newFilename fullPath := toPath + "/" + newFilename
/*
if err := os.MkdirAll(dir, 0777); err != nil {
return
}
*/
// 写到文件中 // 写到文件中
file, err := os.Create(fullPath) file, err := os.Create(fullPath)
@@ -54,7 +53,8 @@ func WriteUrl(url string, toPath string) (path string, ok bool) {
func GetContent(url string) (content []byte, err error) { func GetContent(url string) (content []byte, err error) {
var resp *http.Response var resp *http.Response
resp, err = http.Get(url) resp, err = http.Get(url)
if(resp != nil && resp.Body != nil) { Log(err)
if resp != nil && resp.Body != nil {
defer resp.Body.Close() defer resp.Body.Close()
} else { } else {
} }
@@ -64,30 +64,40 @@ func GetContent(url string) (content []byte, err error) {
var buf []byte var buf []byte
buf, err = ioutil.ReadAll(resp.Body) buf, err = ioutil.ReadAll(resp.Body)
if(err != nil) { if err != nil {
Log(err)
return return
} }
content = buf; content = buf
err = nil err = nil
return return
} }
// 将url ?, #后面的字符串去掉 // 将url ?, #后面的字符串去掉
func trimQueryParams(url string) string { func trimQueryParams(url string) string {
pos := strings.Index(url, "?"); pos := strings.Index(url, "?")
if pos != -1 { if pos != -1 {
url = Substr(url, 0, pos); url = Substr(url, 0, pos)
} }
pos = strings.Index(url, "#"); pos = strings.Index(url, "#")
if pos != -1 { if pos != -1 {
url = Substr(url, 0, pos); url = Substr(url, 0, pos)
} }
pos = strings.Index(url, "!"); pos = strings.Index(url, "!")
if pos != -1 { 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
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:])
}
-31
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}
}
+47 -29
View File
@@ -2,11 +2,13 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"io/ioutil"
"strings" "strings"
// "time"
) )
/* /*
用golang exec 总是说找不到uglifyjs命令, 需要全部路径 用golang exec 总是说找不到uglifyjs命令, 需要全部路径
而且node, npm要在/usr/bin下, 已建ln 而且node, npm要在/usr/bin下, 已建ln
@@ -33,11 +35,13 @@ var jss = []string{"js/jquery-cookie", "js/bootstrap",
"js/common", "js/app/note", "js/app/tag", "js/app/notebook", "js/app/share", "js/common", "js/app/note", "js/app/tag", "js/app/notebook", "js/app/share",
"js/object_id", "js/ZeroClipboard/ZeroClipboard"} "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" var cmdPath = "/usr/local/bin/uglifyjs"
func cmdError(err error) { func cmdError(err error) {
if err != nil { if err != nil {
fmt.Println(err)
fmt.Fprintf(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, "", "") fmt.Fprintf(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, "", "")
} else { } else {
fmt.Println("OK") fmt.Println("OK")
@@ -50,25 +54,26 @@ func compressJs(filename string) {
to := base + filename + "-min.js" to := base + filename + "-min.js"
cmd := exec.Command(cmdPath, source, "-o", to) cmd := exec.Command(cmdPath, source, "-o", to)
_, err := cmd.CombinedOutput() _, err := cmd.CombinedOutput()
fmt.Println(source)
cmdError(err) cmdError(err)
} }
func combineJs() { func combineJs() {
// 生成一个总文件 // 生成一个总文件
cmd := exec.Command("rm", base + "js/all.js") cmd := exec.Command("rm", base+"js/all.js")
_, err := cmd.CombinedOutput() _, err := cmd.CombinedOutput()
cmdError(err) cmdError(err)
for _, js := range jss { for _, js := range jss {
to := base + js + "-min.js" to := base + js + "-min.js"
fmt.Println(to)
compressJs(js) 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() _, err := cmd2.CombinedOutput()
cmdError(err) 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() _, err = cmd2.CombinedOutput()
cmdError(err) cmdError(err)
} }
@@ -77,20 +82,24 @@ func combineJs() {
// 改note-dev->note // 改note-dev->note
func dev() { func dev() {
// 即替换note.js->note-min.js // 即替换note.js->note-min.js
m := map[string]string{"note.js": "note-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", "app.js": "app-min.js",
"page.js": "page-min.js", "page.js": "page-min.js",
"common.js": "common-min.js", "common.js": "common-min.js",
"notebook.js": "notebook-min.js", "notebook.js": "notebook-min.js",
"share.js": "share-min.js", "share.js": "share-min.js",
"tag.js": "tag-min.js", "tag.js": "tag-min.js",
"jquery.slimscroll.js": "jquery.slimscroll-min.js",
"jquery.contextmenu.js": "jquery.contextmenu-min.js", "jquery.contextmenu.js": "jquery.contextmenu-min.js",
"editor/editor.js": "editor/editor-min.js", "editor/editor.js": "editor/editor-min.js",
"/public/mdeditor/editor/scrollLink.js": "/public/mdeditor/editor/scrollLink-min.js", "/public/mdeditor/editor/scrollLink.js": "/public/mdeditor/editor/scrollLink-min.js",
"console.log(o);": "", "console.log(o);": "",
} }
path := "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/src/views/note/note-dev.html" path := base1 + "/src/views/note/note-dev.html"
target := "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/src/views/note/note.html" target := base1 + "/src/views/note/note.html"
bs, _ := ioutil.ReadFile(path) bs, _ := ioutil.ReadFile(path)
content := string(bs) content := string(bs)
@@ -98,34 +107,45 @@ func dev() {
for key, value := range m { for key, value := range m {
content = strings.Replace(content, key, value, -1) 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) ioutil.WriteFile(target, []byte(content), os.ModeAppend)
} }
// 压缩js成一块 // 压缩js成一块
func tinymce() { 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]" // 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", "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 := exec.Command("/bin/sh", "-c", "grunt minify")
cmd.Dir = "/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master" cmd.Dir = base + "/tinymce_4.1.9"
_, err := cmd.CombinedOutput()
cmdError(err) 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() { func main() {
dev(); // 压缩tinymce
// tinymce()
dev()
// 其它零散的需要压缩的js // 其它零散的需要压缩的js
otherJss := []string{"tinymce/tinymce", "js/app/page", "js/contextmenu/jquery.contextmenu", otherJss := []string{"js/main", "js/app/page", "js/contextmenu/jquery.contextmenu",
"mdeditor/editor/scrollLink", "js/jquery.ztree.all-3.5",
"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",
"js/jQuery-slimScroll-1.3.0/jquery.slimscroll", "js/jQuery-slimScroll-1.3.0/jquery.slimscroll",
} }
@@ -136,6 +156,4 @@ func main() {
// 先压缩后合并 // 先压缩后合并
combineJs() combineJs()
// 压缩tinymce
tinymce()
} }
+44
View File
@@ -0,0 +1,44 @@
package service
import (
"github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/db"
"gopkg.in/mgo.v2/bson"
"time"
)
const IMAGE_TYPE = 0
type AlbumService struct {
}
// add album
func (this *AlbumService) AddAlbum(album info.Album) bool {
album.CreatedTime = time.Now()
album.Type = IMAGE_TYPE
return db.Insert(db.Albums, album)
}
// get albums
func (this *AlbumService) GetAlbums(userId string) []info.Album {
albums := []info.Album{}
db.ListByQ(db.Albums, bson.M{"UserId": bson.ObjectIdHex(userId)}, &albums)
return albums
}
// 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),
"UserId": bson.ObjectIdHex(userId),
}) == 0 {
return db.DeleteByIdAndUserId(db.Albums, albumId, userId), ""
}
return false, "has images"
}
// update album name
func (this *AlbumService) UpdateAlbum(albumId, userId, name string) bool {
return db.UpdateByIdAndUserIdField(db.Albums, albumId, userId, "Name", name)
}
+247
View File
@@ -0,0 +1,247 @@
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
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
}
+59 -27
View File
@@ -1,22 +1,32 @@
package service package service
import ( import (
"labix.org/v2/mgo/bson" "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/leanote/leanote/app/info"
"github.com/revel/revel" // "github.com/revel/revel"
. "github.com/leanote/leanote/app/lea" "errors"
"fmt" "fmt"
. "github.com/leanote/leanote/app/lea"
"strconv"
"strings"
) )
// 登录与权限 // 登录与权限 Login & Register
type AuthService struct { type AuthService struct {
} }
// pwd已md5了 // 使用bcrypt认证或者Md5认证
func (this *AuthService) Login(emailOrUsername, pwd string) info.User { // Use bcrypt (Md5 depreciated)
return userService.LoginGetUserInfo(emailOrUsername, Md5(pwd)) 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,18 +40,26 @@ func (this *AuthService) Login(emailOrUsername, pwd string) info.User {
// 1. 添加用户 // 1. 添加用户
// 2. 将leanote共享给我 // 2. 将leanote共享给我
// [ok] // [ok]
func (this *AuthService) Register(email, pwd string) (bool, string) { func (this *AuthService) Register(email, pwd, fromUserId string) (bool, string) {
// 用户是否已存在 // 用户是否已存在
if userService.IsExistsUser(email) { 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) return this.register(user)
} }
func (this *AuthService) register(user info.User) (bool, string) { func (this *AuthService) register(user info.User) (bool, string) {
if userService.AddUser(user) { if userService.AddUser(user) {
// 添加笔记本, 生活, 学习, 工作 // 添加笔记本, 生活, 学习, 工作
userId := user.UserId.Hex()
notebook := info.Notebook{ notebook := info.Notebook{
Seq: -1, Seq: -1,
UserId: user.UserId} UserId: user.UserId}
@@ -50,35 +68,49 @@ func (this *AuthService) register(user info.User) (bool, string) {
notebook.Title = title notebook.Title = title
notebook.NotebookId = objectId notebook.NotebookId = objectId
notebook.UserId = user.UserId notebook.UserId = user.UserId
notebookService.AddNotebook(notebook); notebookService.AddNotebook(notebook)
} }
email := user.Email
// 添加leanote -> 该用户的共享 // 添加leanote -> 该用户的共享
leanoteUserId, _ := revel.Config.String("register.sharedUserId"); // "5368c1aa99c37b029d000001"; registerSharedUserId := configService.GetGlobalStringConfig("registerSharedUserId")
nk1, _ := revel.Config.String("register.sharedUserShareNotebookId"); // 5368c1aa99c37b029d000002" // leanote if registerSharedUserId != "" {
welcomeNoteId, _ := revel.Config.String("register.welcomeNoteId") // "5368c1b919807a6f95000000" // 欢迎来到leanote registerSharedNotebooks := configService.GetGlobalArrMapConfig("registerSharedNotebooks")
registerSharedNotes := configService.GetGlobalArrMapConfig("registerSharedNotes")
registerCopyNoteIds := configService.GetGlobalArrayConfig("registerCopyNoteIds")
if leanoteUserId != "" && nk1 != "" && welcomeNoteId != "" { // 添加共享笔记本
shareService.AddShareNotebook(nk1, 0, leanoteUserId, email); for _, notebook := range registerSharedNotebooks {
shareService.AddShareNote(welcomeNoteId, 0, leanoteUserId, email); perm, _ := strconv.Atoi(notebook["perm"])
shareService.AddShareNotebookToUserId(notebook["notebookId"], perm, registerSharedUserId, userId)
}
// 将welcome copy给我 // 添加共享笔记
note := noteService.CopySharedNote(welcomeNoteId, title2Id["life"].Hex(), leanoteUserId, user.UserId.Hex()); for _, note := range registerSharedNotes {
perm, _ := strconv.Atoi(note["perm"])
shareService.AddShareNoteToUserId(note["noteId"], perm, registerSharedUserId, userId)
}
// 公开为博客 // 复制笔记
noteUpdate := bson.M{"IsBlog": true} for _, noteId := range registerCopyNoteIds {
noteService.UpdateNote(user.UserId.Hex(), user.UserId.Hex(), note.NoteId.Hex(), noteUpdate) 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 // 添加一条userBlog
blogService.UpdateUserBlog(info.UserBlog{UserId: user.UserId, blogService.UpdateUserBlog(info.UserBlog{UserId: user.UserId,
Title: user.Username + " 's Blog", Title: user.Username + " 's Blog",
SubTitle: "love leanote!", SubTitle: "Love Leanote!",
AboutMe: "Hello, I am (^_^)", AboutMe: "Hello, I am (^_^)",
CanComment: true,
}) })
// 添加一个单页面
blogService.AddOrUpdateSingle(user.UserId.Hex(), "", "About Me", "Hello, I am (^_^)")
} }
return true, "" return true, ""
@@ -91,7 +123,7 @@ func (this *AuthService) register(user info.User) (bool, string) {
func (this *AuthService) getUsername(thirdType, thirdUsername string) (username string) { func (this *AuthService) getUsername(thirdType, thirdUsername string) (username string) {
username = thirdType + "-" + thirdUsername username = thirdType + "-" + thirdUsername
i := 1 i := 1
for ;; { for {
if !userService.IsExistsUserByUsername(username) { if !userService.IsExistsUserByUsername(username) {
return return
} }
+1120 -31
View File
File diff suppressed because it is too large. Load diff
+608
View File
@@ -0,0 +1,608 @@
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)
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)
}
}
// 更新用户配置
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.0"
}
+474
View File
@@ -0,0 +1,474 @@
package service
import (
"bytes"
"fmt"
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"html/template"
"net/smtp"
"strconv"
"strings"
"time"
)
// 发送邮件
type EmailService struct {
tpls map[string]*template.Template
}
func NewEmailService() *EmailService {
return &EmailService{tpls: map[string]*template.Template{}}
}
// 发送邮件
var host = ""
var emailPort = ""
var username = ""
var password = ""
func InitEmailFromDb() {
host = configService.GetGlobalStringConfig("emailHost")
emailPort = configService.GetGlobalStringConfig("emailPort")
username = configService.GetGlobalStringConfig("emailUsername")
password = configService.GetGlobalStringConfig("emailPassword")
}
func (this *EmailService) SendEmail(to, subject, body string) (ok bool, e string) {
InitEmailFromDb()
if host == "" || emailPort == "" || username == "" || password == "" {
return
}
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/plain" + "; charset=UTF-8"
}
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+":"+emailPort, auth, username, send_to, msg)
if err != nil {
e = fmt.Sprint(err)
return
}
ok = true
return
}
// AddUser调用
// 可以使用一个goroutine
func (this *EmailService) RegisterSendActiveEmail(userInfo info.User, email string) bool {
token := tokenService.NewToken(userInfo.UserId.Hex(), email, info.TokenActiveEmail)
if token == "" {
return false
}
subject := configService.GetGlobalStringConfig("emailTemplateRegisterSubject")
tpl := configService.GetGlobalStringConfig("emailTemplateRegister")
if tpl == "" {
return false
}
tokenUrl := configService.GetSiteUrl() + "/user/activeEmail?token=" + token
// {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username}
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))),
"user": map[string]interface{}{
"userId": userInfo.UserId.Hex(),
"email": userInfo.Email,
"username": userInfo.Username,
},
}
var ok bool
ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return false
}
// 发送邮件
ok, _ = this.SendEmail(email, subject, tpl)
return ok
}
// 修改邮箱
func (this *EmailService) UpdateEmailSendActiveEmail(userInfo info.User, email string) (ok bool, msg string) {
// 先验证该email是否被注册了
if userService.IsExistsUser(email) {
ok = false
msg = "该邮箱已注册"
return
}
token := tokenService.NewToken(userInfo.UserId.Hex(), email, info.TokenUpdateEmail)
if token == "" {
return
}
subject := configService.GetGlobalStringConfig("emailTemplateUpdateEmailSubject")
tpl := configService.GetGlobalStringConfig("emailTemplateUpdateEmail")
// 发送邮件
tokenUrl := configService.GetSiteUrl() + "/user/updateEmail?token=" + token
// {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.userId} {user.email} {user.username}
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))),
"newEmail": email,
"user": map[string]interface{}{
"userId": userInfo.UserId.Hex(),
"email": userInfo.Email,
"username": userInfo.Username,
},
}
ok, msg, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return
}
// 发送邮件
ok, msg = this.SendEmail(email, subject, tpl)
return
}
func (this *EmailService) FindPwdSendEmail(token, email string) (ok bool, msg string) {
subject := configService.GetGlobalStringConfig("emailTemplateFindPasswordSubject")
tpl := configService.GetGlobalStringConfig("emailTemplateFindPassword")
// 发送邮件
tokenUrl := configService.GetSiteUrl() + "/findPassword/" + token
// {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username}
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl,
"token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail)))}
ok, msg, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return
}
// 发送邮件
ok, msg = this.SendEmail(email, subject, tpl)
return
}
// 发送邀请链接
func (this *EmailService) SendInviteEmail(userInfo info.User, email, content string) bool {
subject := configService.GetGlobalStringConfig("emailTemplateInviteSubject")
tpl := configService.GetGlobalStringConfig("emailTemplateInvite")
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(),
"registerUrl": configService.GetSiteUrl() + "/register?from=" + userInfo.Username,
"content": content,
"user": map[string]interface{}{
"username": userInfo.Username,
"email": userInfo.Email,
},
}
var ok bool
ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return false
}
// 发送邮件
ok, _ = this.SendEmail(email, subject, tpl)
return ok
}
// 发送评论
func (this *EmailService) SendCommentEmail(note info.Note, comment info.BlogComment, userId, content string) bool {
subject := configService.GetGlobalStringConfig("emailTemplateCommentSubject")
tpl := configService.GetGlobalStringConfig("emailTemplateComment")
// title := "评论提醒"
/*
toUserId := note.UserId.Hex()
// title := "评论提醒"
// 表示回复回复的内容, 那么发送给之前回复的
if comment.CommentId != "" {
toUserId = comment.UserId.Hex()
}
toUserInfo := userService.GetUserInfo(toUserId)
sendUserInfo := userService.GetUserInfo(userId)
subject := note.Title + " 收到 " + sendUserInfo.Username + " 的评论";
if comment.CommentId != "" {
subject = "您在 " + note.Title + " 发表的评论收到 " + sendUserInfo.Username;
if userId == note.UserId.Hex() {
subject += "(作者)";
}
subject += " 的评论";
}
*/
toUserId := note.UserId.Hex()
// 表示回复回复的内容, 那么发送给之前回复的
if comment.CommentId != "" {
toUserId = comment.UserId.Hex()
}
toUserInfo := userService.GetUserInfo(toUserId) // 被评论者
sendUserInfo := userService.GetUserInfo(userId) // 评论者
// {siteUrl} {blogUrl}
// {blog.id} {blog.title} {blog.url}
// {commentUser.userId} {commentUser.username} {commentUser.email}
// {commentedUser.userId} {commentedUser.username} {commentedUser.email}
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "blogUrl": configService.GetBlogUrl(),
"blog": map[string]string{
"id": note.NoteId.Hex(),
"title": note.Title,
"url": configService.GetBlogUrl() + "/view/" + note.NoteId.Hex(),
},
"commentContent": content,
// 评论者信息
"commentUser": map[string]interface{}{"userId": sendUserInfo.UserId.Hex(),
"username": sendUserInfo.Username,
"email": sendUserInfo.Email,
"isBlogAuthor": userId == note.UserId.Hex(),
},
// 被评论者信息
"commentedUser": map[string]interface{}{"userId": toUserId,
"username": toUserInfo.Username,
"email": toUserInfo.Email,
"isBlogAuthor": toUserId == note.UserId.Hex(),
},
}
ok := false
ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return false
}
// 发送邮件
ok, _ = this.SendEmail(toUserInfo.Email, subject, tpl)
return ok
}
// 验证模板是否正确
func (this *EmailService) ValidTpl(str string) (ok bool, msg string) {
defer func() {
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err)
}
}()
header := configService.GetGlobalStringConfig("emailTemplateHeader")
footer := configService.GetGlobalStringConfig("emailTemplateFooter")
str = strings.Replace(str, "{{header}}", header, -1)
str = strings.Replace(str, "{{footer}}", footer, -1)
_, err := template.New("tpl name").Parse(str)
if err != nil {
msg = fmt.Sprint(err)
return
}
ok = true
return
}
// ok, msg, subject, tpl
func (this *EmailService) getTpl(str string) (ok bool, msg string, tpl *template.Template) {
defer func() {
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err)
}
}()
var err error
var has bool
if tpl, has = this.tpls[str]; !has {
tpl, err = template.New("tpl name").Parse(str)
if err != nil {
msg = fmt.Sprint(err)
return
}
this.tpls[str] = tpl
}
ok = true
return
}
// 通过subject, body和值得到内容
func (this *EmailService) renderEmail(subject, body string, values map[string]interface{}) (ok bool, msg string, o string, b string) {
ok = false
msg = ""
defer func() { // 必须要先声明defer,否则不能捕获到panic异常
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err) // 这里的err其实就是panic传入的内容,
}
}()
var tpl *template.Template
values["siteUrl"] = configService.GetSiteUrl()
// subject
if subject != "" {
ok, msg, tpl = this.getTpl(subject)
if !ok {
return
}
var buffer bytes.Buffer
err := tpl.Execute(&buffer, values)
if err != nil {
msg = fmt.Sprint(err)
return
}
o = buffer.String()
} else {
o = ""
}
// content
header := configService.GetGlobalStringConfig("emailTemplateHeader")
footer := configService.GetGlobalStringConfig("emailTemplateFooter")
body = strings.Replace(body, "{{header}}", header, -1)
body = strings.Replace(body, "{{footer}}", footer, -1)
values["subject"] = o
ok, msg, tpl = this.getTpl(body)
if !ok {
return
}
var buffer2 bytes.Buffer
err := tpl.Execute(&buffer2, values)
if err != nil {
msg = fmt.Sprint(err)
return
}
b = buffer2.String()
return
}
// 发送email给用户
// 需要记录
func (this *EmailService) SendEmailToUsers(users []info.User, subject, body string) (ok bool, msg string) {
if users == nil || len(users) == 0 {
msg = "no users"
return
}
// 尝试renderHtml
ok, msg, _, _ = this.renderEmail(subject, body, map[string]interface{}{})
if !ok {
Log(msg)
return
}
go func() {
for _, user := range users {
LogJ(user)
m := map[string]interface{}{}
m["userId"] = user.UserId.Hex()
m["username"] = user.Username
m["email"] = user.Email
ok2, msg2, subject2, body2 := this.renderEmail(subject, body, m)
ok = ok2
msg = msg2
if ok2 {
sendOk, msg := this.SendEmail(user.Email, subject2, body2)
this.AddEmailLog(user.Email, subject, body, sendOk, msg) // 把模板记录下
// 记录到Email Log
if sendOk {
// Log("ok " + user.Email)
} else {
// Log("no " + user.Email)
}
} else {
// Log(msg);
}
}
}()
return
}
func (this *EmailService) SendEmailToEmails(emails []string, subject, body string) (ok bool, msg string) {
if emails == nil || len(emails) == 0 {
msg = "no emails"
return
}
// 尝试renderHtml
ok, msg, _, _ = this.renderEmail(subject, body, map[string]interface{}{})
if !ok {
Log(msg)
return
}
// go func() {
for _, email := range emails {
if email == "" {
continue
}
m := map[string]interface{}{}
m["email"] = email
ok, msg, subject, body = this.renderEmail(subject, body, m)
if ok {
sendOk, msg := this.SendEmail(email, subject, body)
this.AddEmailLog(email, subject, body, sendOk, msg)
// 记录到Email Log
if sendOk {
Log("ok " + email)
} else {
Log("no " + email)
}
} else {
Log(msg)
}
}
// }()
return
}
// 添加邮件日志
func (this *EmailService) AddEmailLog(email, subject, body string, ok bool, msg string) {
log := info.EmailLog{LogId: bson.NewObjectId(), Email: email, Subject: subject, Body: body, Ok: ok, Msg: msg, CreatedTime: time.Now()}
db.Insert(db.EmailLogs, log)
}
// 展示邮件日志
func (this *EmailService) DeleteEmails(ids []string) bool {
idsO := make([]bson.ObjectId, len(ids))
for i, id := range ids {
idsO[i] = bson.ObjectIdHex(id)
}
db.DeleteAll(db.EmailLogs, bson.M{"_id": bson.M{"$in": idsO}})
return true
}
func (this *EmailService) ListEmailLogs(pageNumber, pageSize int, sortField string, isAsc bool, email string) (page info.Page, emailLogs []info.EmailLog) {
emailLogs = []info.EmailLog{}
skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, sortField, isAsc)
query := bson.M{}
if email != "" {
query["Email"] = bson.M{"$regex": bson.RegEx{".*?" + email + ".*", "i"}}
}
q := db.EmailLogs.Find(query)
// 总记录数
count, _ := q.Count()
// 列表
q.Sort(sortFieldR).
Skip(skipNum).
Limit(pageSize).
All(&emailLogs)
page = info.NewPage(pageNumber, pageSize, count, nil)
return
}
Loaded 100 of 2768 files, more files were not shown because too many files have changed in this diff. Show more