From 593d2c29653273793cfb7b0b2936ae314d4345af Mon Sep 17 00:00:00 2001 From: life Date: Wed, 22 Oct 2014 16:20:45 +0800 Subject: [PATCH] v1.0 beta init --- app/controllers/AuthController.go | 90 +- app/controllers/BaseController.go | 67 + app/controllers/BlogController.go | 498 +++- app/controllers/CaptchaController.go | 43 + app/controllers/FileController.go | 53 +- app/controllers/IndexController.go | 4 +- app/controllers/NoteController.go | 123 +- app/controllers/UserController.go | 99 +- app/controllers/admin/AdminBaseController.go | 8 + app/controllers/admin/AdminController.go | 13 + app/controllers/admin/AdminData.go | 114 + app/controllers/admin/AdminEmailController.go | 233 ++ .../admin/AdminSettingController.go | 61 +- .../admin/AdminUpgradeController.go | 18 + app/controllers/admin/init.go | 8 + app/controllers/init.go | 11 +- app/db/Mgo.go | 26 +- app/i18n/i18n.go | 23 +- app/info/BlogInfo.go | 78 +- app/info/Configinfo.go | 22 +- app/info/EmailLogInfo.go | 19 + app/info/NoteInfo.go | 21 +- app/info/ReportInfo.go | 19 + app/info/SessionInfo.go | 19 + app/info/UserInfo.go | 1 + app/init.go | 81 +- app/lea/Email.go | 13 +- app/lea/File.go | 10 + app/lea/Vd.go | 145 + app/lea/captcha/Captcha.go | 399 +++ app/lea/html2image/ToImage.go | 10 + app/lea/memcache/Memcache.go | 46 +- app/lea/memcache/init.go | 11 - app/lea/netutil/NetUtil.go | 13 +- app/lea/{ => route}/Route.go | 31 +- app/lea/session/MSession.go | 38 + app/lea/session/session.go | 219 +- app/release/release.go | 9 +- app/service/AuthService.go | 40 +- app/service/BlogService.go | 348 ++- app/service/ConfigService.go | 562 +++- app/service/EmailService.go | 474 ++++ app/service/NoteService.go | 38 +- app/service/NotebookService.go | 40 +- app/service/PwdService.go | 11 +- app/service/SessionService.go | 71 + app/service/TrashService.go | 8 +- app/service/UpgradeService.go | 27 + app/service/UserService.go | 157 +- app/service/init.go | 15 +- app/test/TestNoteService.go | 3 +- app/views/Admin/Blog/list.html | 63 +- .../blog.html => Data/configuration.html} | 22 +- app/views/Admin/Data/index.html | 115 + app/views/Admin/Email/emailDialog.html | 76 + app/views/Admin/Email/list.html | 191 ++ app/views/Admin/Email/page.html | 33 + app/views/Admin/Email/send.html | 85 + app/views/Admin/Email/sendToUsers.html | 105 + app/views/Admin/Email/set.html | 62 + app/views/Admin/Email/template.html | 325 +++ app/views/Admin/Setting/demo.html | 4 +- app/views/Admin/Setting/shareNote.html | 173 ++ app/views/Admin/User/add.html | 2 +- app/views/Admin/User/list.html | 62 +- app/views/Admin/footer.html | 52 +- app/views/Admin/index.html | 110 +- app/views/Admin/nav.html | 151 +- app/views/Admin/top.html | 33 +- app/views/Blog/about_me.html | 4 +- app/views/Blog/comment.html | 188 +- app/views/Blog/footer.html | 42 +- app/views/Blog/header.html | 42 +- app/views/Blog/index.html | 23 +- app/views/Blog/search.html | 18 +- app/views/Blog/set.html | 149 +- app/views/Blog/view.html | 98 +- app/views/Errors/500.html | 2 +- app/views/Home/header.html | 56 +- app/views/Home/login.html | 40 +- app/views/Home/register.html | 6 +- app/views/Html2Image/index.html | 102 + app/views/Html2Image/test.html | 4 + app/views/Note/note-dev.html | 520 ++-- app/views/Note/note.html | 524 ++-- app/views/Oauth/oauth_callback_error.html | 36 +- .../Share/note_notebook_share_user_infos.html | 29 +- app/views/User/account.html | 272 ++ app/views/User/active_email.html | 42 +- app/views/User/update_email.html | 48 +- messages/blog.en | 64 +- messages/blog.zh | 58 +- messages/msg.en | 99 +- messages/msg.zh | 118 +- public/admin/config.codekit | 2004 ++++++++++++++ public/admin/css/admin.css | 1 - public/admin/css/admin.less | 2 +- public/admin/js/admin.js | 14 +- public/admin/js/min/admin-min.js | 1 + public/css/blog/basic.less | 255 ++ public/css/blog/blog_daqi.css | 366 +++ public/css/blog/blog_daqi.less | 5 +- public/css/blog/blog_default.css | 366 +++ public/css/blog/blog_default.less | 4 +- public/css/blog/blog_left_fixed.css | 366 +++ public/css/blog/blog_left_fixed.less | 4 +- public/css/blog/comment.css | 325 +++ public/css/blog/comment.less | 375 +++ public/css/blog/mobile.less | 157 ++ public/css/blog/p.css | 108 +- public/css/blog/p.less | 159 +- public/css/bootstrap.css | 1 - public/css/config.codekit | 2429 +++++++++++++++++ public/css/editor/editor-writting-mode.css | 3 + public/css/editor/editor-writting-mode.less | 3 + public/css/editor/editor.css | 18 +- public/css/editor/editor.less | 8 + .../font-awesome-4.2.0/css/font-awesome.css | 1672 ++++++++++++ .../css/font-awesome.min.css | 4 + .../font-awesome-4.2.0/fonts/FontAwesome.otf | Bin 0 -> 85908 bytes .../fonts/fontawesome-webfont.eot | Bin 0 -> 56006 bytes .../fonts/fontawesome-webfont.svg | 520 ++++ .../fonts/fontawesome-webfont.ttf | Bin 0 -> 112160 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 65452 bytes .../less/bordered-pulled.less | 16 + public/css/font-awesome-4.2.0/less/core.less | 11 + .../font-awesome-4.2.0/less/fixed-width.less | 6 + .../font-awesome-4.2.0/less/font-awesome.less | 17 + public/css/font-awesome-4.2.0/less/icons.less | 552 ++++ .../css/font-awesome-4.2.0/less/larger.less | 13 + public/css/font-awesome-4.2.0/less/list.less | 19 + .../css/font-awesome-4.2.0/less/mixins.less | 25 + public/css/font-awesome-4.2.0/less/path.less | 14 + .../less/rotated-flipped.less | 20 + .../css/font-awesome-4.2.0/less/spinning.less | 29 + .../css/font-awesome-4.2.0/less/stacked.less | 20 + .../font-awesome-4.2.0/less/variables.less | 561 ++++ .../scss/_bordered-pulled.scss | 16 + public/css/font-awesome-4.2.0/scss/_core.scss | 11 + .../font-awesome-4.2.0/scss/_fixed-width.scss | 6 + .../css/font-awesome-4.2.0/scss/_icons.scss | 552 ++++ .../css/font-awesome-4.2.0/scss/_larger.scss | 13 + public/css/font-awesome-4.2.0/scss/_list.scss | 19 + .../css/font-awesome-4.2.0/scss/_mixins.scss | 25 + public/css/font-awesome-4.2.0/scss/_path.scss | 14 + .../scss/_rotated-flipped.scss | 20 + .../font-awesome-4.2.0/scss/_spinning.scss | 29 + .../css/font-awesome-4.2.0/scss/_stacked.scss | 20 + .../font-awesome-4.2.0/scss/_variables.scss | 561 ++++ .../font-awesome-4.2.0/scss/font-awesome.scss | 17 + public/css/index.css | 89 +- public/css/index.less | 95 +- public/css/theme/basic.less | 399 ++- public/css/theme/default.css | 756 ++++- public/css/theme/default.less | 124 +- public/css/theme/mobile.less | 269 ++ public/css/theme/simple.css | 747 ++++- public/css/theme/simple.less | 113 +- public/css/theme/writting-overwrite.css | 394 ++- public/css/theme/writting-overwrite.less | 2 +- public/css/theme/writting.css | 386 ++- public/css/toImage.css | 92 + public/css/toImage.less | 98 + public/images/blog/tag.png | Bin 0 -> 3142 bytes public/images/blog/theme/default.png | Bin 0 -> 111868 bytes public/images/blog/theme/elegent.png | Bin 0 -> 85060 bytes public/images/blog/theme/left_nav_fix.png | Bin 0 -> 139058 bytes public/images/home/mobile.png | Bin 293110 -> 131154 bytes public/images/home/preview2.png | Bin 300289 -> 705602 bytes public/js/all.js | 4 +- public/js/app/attachment_upload.js | 168 +- public/js/app/blog/common.js | 207 ++ public/js/app/blog/nav.js | 85 - public/js/app/blog/view.js | 509 ++++ public/js/app/note-min.js | 2 +- public/js/app/note.js | 192 +- public/js/app/notebook-min.js | 2 +- public/js/app/notebook.js | 52 +- public/js/app/page-min.js | 2 +- public/js/app/page.js | 1395 +++++----- public/js/app/share-min.js | 2 +- public/js/app/share.js | 52 +- public/js/app/tag-min.js | 2 +- public/js/app/tag.js | 15 +- public/js/bootstrap-dialog.min.js | 1 + public/js/bootstrap-hover-dropdown.js | 111 + public/js/common-min.js | 2 +- public/js/common.js | 324 ++- .../js/contextmenu/jquery.contextmenu-min.js | 2 +- public/js/contextmenu/jquery.contextmenu.js | 2 +- public/js/fastclick.js | 822 ++++++ public/js/i18n/blog.en.js | 16 + public/js/i18n/blog.zh.js | 16 + public/js/i18n/msg.en.js | 17 +- public/js/i18n/msg.zh.js | 17 +- public/js/jquery-cookie-min.js | 2 +- public/js/jquery-cookie.js | 9 +- public/js/jquery.mobile-1.4.4.min.js | 10 + public/js/jquery.qrcode.min.js | 28 + public/js/jsrender.js | 1626 +++++++++++ public/js/main-min.js | 1 + public/js/main.js | 105 + public/mdeditor/editor/editor.css | 110 - .../editor/google-code-prettify/prettify.css | 2 +- public/mdeditor/editor/mathJax-min.js | 2 +- public/mdeditor/editor/mathJax.js | 9 + public/mdeditor/editor/mdeditor.js | 377 +++ .../pagedown/local/Markdown.local.en-min.js | 1 + .../pagedown/local/Markdown.local.en.js | 45 + .../pagedown/local/Markdown.local.zh-min.js | 2 +- .../pagedown/local/Markdown.local.zh.js | 16 +- public/tinymce/plugins/codemirror/plugin.js | 3 +- .../tinymce/plugins/codemirror/plugin.min.js | 2 +- public/tinymce/plugins/leaui_image/index.html | 2 + public/tinymce/plugins/leaui_image/plugin.js | 14 +- .../tinymce/plugins/leaui_image/plugin.min.js | 2 +- .../plugins/leaui_image/public/css/style.css | 42 +- .../plugins/leaui_image/public/css/style.less | 190 -- .../leaui_image/public/js/for_editor.js | 10 +- .../plugins/leaui_image/public/js/main.js | 21 +- .../plugins/paste/classes/Clipboard.js | 56 +- .../tinymce/plugins/paste/classes/Plugin.js | 24 + public/tinymce/plugins/paste/plugin.dev.js | 2 +- public/tinymce/plugins/paste/plugin.js | 80 +- public/tinymce/plugins/paste/plugin.min.js | 2 +- 225 files changed, 27217 insertions(+), 3675 deletions(-) create mode 100644 app/controllers/CaptchaController.go create mode 100644 app/controllers/admin/AdminData.go create mode 100644 app/controllers/admin/AdminEmailController.go create mode 100644 app/controllers/admin/AdminUpgradeController.go create mode 100644 app/info/EmailLogInfo.go create mode 100644 app/info/ReportInfo.go create mode 100644 app/info/SessionInfo.go create mode 100644 app/lea/Vd.go create mode 100644 app/lea/captcha/Captcha.go create mode 100644 app/lea/html2image/ToImage.go delete mode 100644 app/lea/memcache/init.go rename app/lea/{ => route}/Route.go (60%) create mode 100644 app/lea/session/MSession.go create mode 100644 app/service/EmailService.go create mode 100644 app/service/SessionService.go create mode 100644 app/service/UpgradeService.go rename app/views/Admin/{Setting/blog.html => Data/configuration.html} (56%) create mode 100644 app/views/Admin/Data/index.html create mode 100644 app/views/Admin/Email/emailDialog.html create mode 100644 app/views/Admin/Email/list.html create mode 100644 app/views/Admin/Email/page.html create mode 100644 app/views/Admin/Email/send.html create mode 100644 app/views/Admin/Email/sendToUsers.html create mode 100644 app/views/Admin/Email/set.html create mode 100644 app/views/Admin/Email/template.html create mode 100644 app/views/Admin/Setting/shareNote.html create mode 100644 app/views/Html2Image/index.html create mode 100644 app/views/Html2Image/test.html create mode 100644 app/views/User/account.html create mode 100644 public/admin/config.codekit create mode 100644 public/admin/js/min/admin-min.js create mode 100644 public/css/blog/comment.css create mode 100644 public/css/blog/comment.less create mode 100644 public/css/blog/mobile.less create mode 100644 public/css/config.codekit create mode 100644 public/css/font-awesome-4.2.0/css/font-awesome.css create mode 100644 public/css/font-awesome-4.2.0/css/font-awesome.min.css create mode 100644 public/css/font-awesome-4.2.0/fonts/FontAwesome.otf create mode 100644 public/css/font-awesome-4.2.0/fonts/fontawesome-webfont.eot create mode 100644 public/css/font-awesome-4.2.0/fonts/fontawesome-webfont.svg create mode 100644 public/css/font-awesome-4.2.0/fonts/fontawesome-webfont.ttf create mode 100644 public/css/font-awesome-4.2.0/fonts/fontawesome-webfont.woff create mode 100644 public/css/font-awesome-4.2.0/less/bordered-pulled.less create mode 100644 public/css/font-awesome-4.2.0/less/core.less create mode 100644 public/css/font-awesome-4.2.0/less/fixed-width.less create mode 100644 public/css/font-awesome-4.2.0/less/font-awesome.less create mode 100644 public/css/font-awesome-4.2.0/less/icons.less create mode 100644 public/css/font-awesome-4.2.0/less/larger.less create mode 100644 public/css/font-awesome-4.2.0/less/list.less create mode 100644 public/css/font-awesome-4.2.0/less/mixins.less create mode 100644 public/css/font-awesome-4.2.0/less/path.less create mode 100644 public/css/font-awesome-4.2.0/less/rotated-flipped.less create mode 100644 public/css/font-awesome-4.2.0/less/spinning.less create mode 100644 public/css/font-awesome-4.2.0/less/stacked.less create mode 100644 public/css/font-awesome-4.2.0/less/variables.less create mode 100644 public/css/font-awesome-4.2.0/scss/_bordered-pulled.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_core.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_fixed-width.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_icons.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_larger.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_list.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_mixins.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_path.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_rotated-flipped.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_spinning.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_stacked.scss create mode 100644 public/css/font-awesome-4.2.0/scss/_variables.scss create mode 100644 public/css/font-awesome-4.2.0/scss/font-awesome.scss create mode 100644 public/css/theme/mobile.less create mode 100644 public/css/toImage.css create mode 100644 public/css/toImage.less create mode 100644 public/images/blog/tag.png create mode 100644 public/images/blog/theme/default.png create mode 100644 public/images/blog/theme/elegent.png create mode 100644 public/images/blog/theme/left_nav_fix.png create mode 100644 public/js/app/blog/common.js delete mode 100644 public/js/app/blog/nav.js create mode 100644 public/js/app/blog/view.js create mode 100644 public/js/bootstrap-dialog.min.js create mode 100644 public/js/bootstrap-hover-dropdown.js create mode 100644 public/js/fastclick.js create mode 100644 public/js/i18n/blog.en.js create mode 100644 public/js/i18n/blog.zh.js create mode 100644 public/js/jquery.mobile-1.4.4.min.js create mode 100644 public/js/jquery.qrcode.min.js create mode 100644 public/js/jsrender.js create mode 100644 public/js/main-min.js create mode 100644 public/js/main.js delete mode 100644 public/mdeditor/editor/editor.css create mode 100644 public/mdeditor/editor/mdeditor.js create mode 100644 public/mdeditor/editor/pagedown/local/Markdown.local.en-min.js create mode 100644 public/mdeditor/editor/pagedown/local/Markdown.local.en.js delete mode 100644 public/tinymce/plugins/leaui_image/public/css/style.less diff --git a/app/controllers/AuthController.go b/app/controllers/AuthController.go index d49f2e3..c12dc08 100644 --- a/app/controllers/AuthController.go +++ b/app/controllers/AuthController.go @@ -4,6 +4,7 @@ import ( "github.com/revel/revel" "github.com/leanote/leanote/app/info" . "github.com/leanote/leanote/app/lea" +// "strconv" ) // 用户登录/注销/找回密码 @@ -14,48 +15,88 @@ 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["subTitle"] = c.Message("login") c.RenderArgs["email"] = email + c.RenderArgs["from"] = from c.RenderArgs["openRegister"] = openRegister + sessionId := c.Session.Id() + if sessionService.LoginTimesIsOver(sessionId) { + c.RenderArgs["needCaptcha"] = true + } + + c.SetLocale() + if c.Has("demo") { c.RenderArgs["demo"] = true c.RenderArgs["email"] = "demo@leanote.com" } return c.RenderTemplate("home/login.html") } -func (c Auth) DoLogin(email, pwd string) revel.Result { + +// 为了demo和register +func (c Auth) doLogin(email, pwd string) revel.Result { + sessionId := c.Session.Id() + var msg = "" + userInfo := authService.Login(email, pwd) if userInfo.Email != "" { c.SetSession(userInfo) - // 必须要redirect, 不然用户刷新会重复提交登录信息 -// return c.Redirect("/") - configService.InitUserConfigs(userInfo.UserId.Hex()) + sessionService.ClearLoginTimes(sessionId) return c.RenderJson(info.Re{Ok: true}) + } else { + // 登录错误, 则错误次数++ + msg = "wrongUsernameOrPassword" } -// 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 := authService.Login(email, pwd) + if userInfo.Email != "" { + c.SetSession(userInfo) + sessionService.ClearLoginTimes(sessionId) + return c.RenderJson(info.Re{Ok: true}) + } else { + // 登录错误, 则错误次数++ + msg = "wrongUsernameOrPassword" + sessionService.IncrLoginTimes(sessionId) + } + } + + return c.RenderJson(info.Re{Ok: false, Item: sessionService.LoginTimesIsOver(sessionId) , Msg: c.Message(msg)}) } // 注销 func (c Auth) Logout() revel.Result { + sessionId := c.Session.Id() + sessionService.Clear(sessionId) c.ClearSession() return c.Redirect("/login") } // 体验一下 func (c Auth) Demo() revel.Result { - c.DoLogin("demo@leanote.com", "demo@leanote.com") + c.doLogin(configService.GetGlobalStringConfig("demoUsername"), configService.GetGlobalStringConfig("demoPassword")) return c.Redirect("/note") } //-------- // 注册 -func (c Auth) Register() revel.Result { +func (c Auth) Register(from string) revel.Result { if !openRegister { return c.Redirect("/index") } + c.SetLocale() + c.RenderArgs["from"] = from c.RenderArgs["title"] = c.Message("register") c.RenderArgs["subTitle"] = c.Message("register") @@ -68,21 +109,11 @@ func (c Auth) DoRegister(email, pwd string) revel.Result { re := info.NewRe(); - if email == "" { - re.Msg = c.Message("inputEmail") - return c.RenderJson(re) - } else if !IsEmail(email) { - re.Msg = c.Message("wrongEmail") - return c.RenderJson(re) + if re.Ok, re.Msg = Vd("email", email); !re.Ok { + return c.RenderRe(re); } - - // 密码 - if pwd == "" { - re.Msg = c.Message("inputPassword") - return c.RenderJson(re) - } else if len(pwd) < 6 { - re.Msg = c.Message("wrongPassword") - return c.RenderJson(re) + if re.Ok, re.Msg = Vd("password", pwd); !re.Ok { + return c.RenderRe(re); } // 注册 @@ -90,10 +121,10 @@ func (c Auth) DoRegister(email, pwd string) revel.Result { // 注册成功, 则立即登录之 if re.Ok { - c.DoLogin(email, pwd) + c.doLogin(email, pwd) } - return c.RenderJson(re) + return c.RenderRe(re) } //-------- @@ -130,13 +161,12 @@ func (c Auth) FindPassword2(token string) revel.Result { // 找回密码修改密码 func (c Auth) FindPasswordUpdate(token, pwd string) revel.Result { re := info.NewRe(); - - re.Ok, re.Msg = IsGoodPwd(pwd) - if !re.Ok { - return c.RenderJson(re) + + if re.Ok, re.Msg = Vd("password", pwd); !re.Ok { + return c.RenderRe(re); } // 修改之 re.Ok, re.Msg = pwdService.UpdatePwd(token, pwd) - return c.RenderJson(re) + return c.RenderRe(re) } diff --git a/app/controllers/BaseController.go b/app/controllers/BaseController.go index 34e0862..d86a99e 100644 --- a/app/controllers/BaseController.go +++ b/app/controllers/BaseController.go @@ -5,11 +5,13 @@ import ( "gopkg.in/mgo.v2/bson" "encoding/json" "github.com/leanote/leanote/app/info" +// . "github.com/leanote/leanote/app/lea" // "io/ioutil" // "fmt" "math" "strconv" "strings" + "bytes" ) // 公用Controller, 其它Controller继承它 @@ -54,15 +56,21 @@ func (c BaseController) GetUsername() string { // 得到用户信息 func (c BaseController) GetUserInfo() info.User { if userId, ok := c.Session["UserId"]; ok && userId != "" { + return userService.GetUserInfo(userId); + /* notebookWidth, _ := strconv.Atoi(c.Session["NotebookWidth"]) noteListWidth, _ := strconv.Atoi(c.Session["NoteListWidth"]) + mdEditorWidth, _ := strconv.Atoi(c.Session["MdEditorWidth"]) + LogJ(c.Session) user := info.User{UserId: bson.ObjectIdHex(userId), Email: c.Session["Email"], + Logo: c.Session["Logo"], Username: c.Session["Username"], UsernameRaw: c.Session["UsernameRaw"], Theme: c.Session["Theme"], NotebookWidth: notebookWidth, NoteListWidth: noteListWidth, + MdEditorWidth: mdEditorWidth, } if c.Session["Verified"] == "1" { user.Verified = true @@ -71,10 +79,19 @@ func (c BaseController) GetUserInfo() info.User { user.LeftIsMin = true } return user + */ } return info.User{} } +// 这里的session都是cookie中的, 与数据库session无关 +func (c BaseController) GetSession(key string) string { + v, ok := c.Session[key] + if !ok { + v = "" + } + return v +} func (c BaseController) SetSession(userInfo info.User) { if userInfo.UserId.Hex() != "" { c.Session["UserId"] = userInfo.UserId.Hex() @@ -82,6 +99,7 @@ func (c BaseController) SetSession(userInfo info.User) { c.Session["Username"] = userInfo.Username c.Session["UsernameRaw"] = userInfo.UsernameRaw c.Session["Theme"] = userInfo.Theme + c.Session["Logo"] = userInfo.Logo c.Session["NotebookWidth"] = strconv.Itoa(userInfo.NotebookWidth) c.Session["NoteListWidth"] = strconv.Itoa(userInfo.NoteListWidth) @@ -165,6 +183,12 @@ func (c BaseController) SetLocale() string { lang = "en"; } c.RenderArgs["locale"] = lang; + c.RenderArgs["siteUrl"] = siteUrl; + + c.RenderArgs["blogUrl"] = configService.GetBlogUrl() + c.RenderArgs["leaUrl"] = configService.GetLeaUrl() + c.RenderArgs["noteUrl"] = configService.GetNoteUrl() + return lang } @@ -172,4 +196,47 @@ func (c BaseController) SetLocale() string { func (c BaseController) SetUserInfo() { userInfo := c.GetUserInfo() c.RenderArgs["userInfo"] = 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 { + 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) + } + } + return c.RenderJson(re) } \ No newline at end of file diff --git a/app/controllers/BlogController.go b/app/controllers/BlogController.go index 98062b2..7c9fde2 100644 --- a/app/controllers/BlogController.go +++ b/app/controllers/BlogController.go @@ -1,6 +1,8 @@ package controllers import ( + "strings" + "time" "github.com/revel/revel" // "encoding/json" "gopkg.in/mgo.v2/bson" @@ -17,142 +19,283 @@ type Blog struct { BaseController } -//--------------------------- -// 后台 note<->blog - -// 设置/取消Blog; 置顶 -func (c Blog) SetNote2Blog(noteId string, isBlog, isTop bool) revel.Result { - if isTop { - isBlog = true - } - if !isBlog { - isTop = false - } - noteUpdate := bson.M{"IsBlog": isBlog, "IsTop": isTop} - re := noteService.UpdateNote(c.GetUserId(), c.GetUserId(), - noteId, noteUpdate) - return c.RenderJson(re) -} - -// 设置notebook <-> blog -func (c Blog) SetNotebook2Blog(notebookId string, isBlog bool) revel.Result { - noteUpdate := bson.M{"IsBlog": isBlog} - re := notebookService.UpdateNotebook(c.GetUserId(), - notebookId, noteUpdate) - return c.RenderJson(re) -} - //----------------------------- // 前台 +// 域名, 没用 +func (c Blog) domain() (ok bool, userBlog info.UserBlog) { + return +} + +// 各种地址设置 +func (c Blog) setUrl(userBlog info.UserBlog, userInfo info.User) { + // 主页 http://leanote.com/blog/life or http://blog.leanote.com/life or http:// xxxx.leanote.com or aa.com + var indexUrl, viewUrl, searchUrl, cateUrl, aboutMeUrl, staticUrl string + host := c.Request.Request.Host + staticUrl = configService.GetUserUrl(strings.Split(host, ":")[0]) + // staticUrl == host, 为保证同源!!! 只有host, http://leanote.com, http://blog/leanote.com + // life.leanote.com, lealife.com + if userBlog.Domain != "" && configService.AllowCustomDomain() { + // ok + indexUrl = configService.GetUserUrl(userBlog.Domain) + cateUrl = indexUrl + "/cate" // /xxxxx + viewUrl = indexUrl + "/view" // /xxxxx + searchUrl = indexUrl + "/search" // /xxxxx + aboutMeUrl = indexUrl + "/aboutMe" + } else if userBlog.SubDomain != "" { + indexUrl = configService.GetUserSubUrl(userBlog.SubDomain) + cateUrl = indexUrl + "/cate" // /xxxxx + viewUrl = indexUrl + "/view" // /xxxxx + searchUrl = indexUrl + "/search" // /xxxxx + aboutMeUrl = indexUrl + "/aboutMe" + } else { + // ok + blogUrl := configService.GetBlogUrl() + userIdOrEmail := "" + if userInfo.Username != "" { + userIdOrEmail = userInfo.Username + } else if userInfo.Email != "" { + userIdOrEmail = userInfo.Email + } else { + userIdOrEmail = userInfo.UserId.Hex() + } + indexUrl = blogUrl + "/" + userIdOrEmail + cateUrl = blogUrl + "/cate" // /notebookId + viewUrl = blogUrl + "/view" // /xxxxx + searchUrl = blogUrl + "/search/" + userIdOrEmail // /xxxxx + aboutMeUrl = blogUrl + "/aboutMe/" + userIdOrEmail + } + + // 分类 + // 搜索 + // 查看 + c.RenderArgs["indexUrl"] = indexUrl + c.RenderArgs["cateUrl"] = cateUrl + c.RenderArgs["viewUrl"] = viewUrl + c.RenderArgs["searchUrl"] = searchUrl + c.RenderArgs["aboutMeUrl"] = aboutMeUrl + c.RenderArgs["staticUrl"] = staticUrl +} + +// 公共 +func (c Blog) blogCommon(userId string, userBlog info.UserBlog, userInfo info.User) (ok bool) { + if userInfo.UserId == "" { + userInfo = userService.GetUserInfoByAny(userId) + if userInfo.UserId == "" { + return + } + } + c.RenderArgs["userInfo"] = userInfo + + // 分类导航 + c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId) + // 最新笔记 + c.getRecentBlogs(userId) + // 语言, url地址 + c.SetLocale(); + c.RenderArgs["isMe"] = userId == c.GetUserId() + + // 得到博客设置信息 + if userBlog.UserId == "" { + userBlog = blogService.GetUserBlog(userId) + } + c.RenderArgs["userBlog"] = userBlog + + c.setUrl(userBlog, userInfo) + + return true +} + +// 跨域判断是否是我的博客 +func (c Blog) IsMe(userId string) revel.Result { + var js = "" + if c.GetUserId() == userId { + js = "$('.is-me').removeClass('hide');" + } + return c.RenderText(js); +} // 进入某个用户的博客 var blogPageSize = 5 var searchBlogPageSize = 30 -func (c Blog) Index(userId string, notebookId string) revel.Result { - // 用户id为空, 转至博客平台 - if userId == "" { - userId = leanoteUserId; + +// 分类 /cate/xxxxxxxx?notebookId=1212 +func (c Blog) Cate(notebookId string) revel.Result { + if notebookId == "" { + return c.E404() + } + // 自定义域名 + hasDomain, userBlog := c.domain() + userId := "" + if hasDomain { + userId = userBlog.UserId.Hex() } - // userId可能是 username, email - userInfo := userService.GetUserInfoByAny(userId) - if userInfo.UserId == "" { + var notebook info.Notebook + notebook = notebookService.GetNotebookById(notebookId) + if !notebook.IsBlog { + return c.E404() + } + if userId != "" && userId != notebook.UserId.Hex() { + return c.E404() + } + userId = notebook.UserId.Hex() + + if !c.blogCommon(userId, userBlog, info.User{}) { return c.E404() } - userId = userInfo.UserId.Hex() - c.isMe(userId) - - c.RenderArgs["userInfo"] = userInfo - - // 得到博客设置信息 - userBlog := blogService.GetUserBlog(userId) - c.RenderArgs["userBlog"] = userBlog - - var notebook info.Notebook - if notebookId != "" { - notebook = notebookService.GetNotebook(notebookId, userId) - if !notebook.IsBlog { - return c.E404() - } - - c.RenderArgs["title"] = userBlog.Title + " - 分类: " + notebook.Title - } else { - c.RenderArgs["title"] = userBlog.Title - } // 分页的话, 需要分页信息, totalPage, curPage page := c.GetPage() - count, blogs := blogService.ListBlogs(userId, notebookId, page, blogPageSize, "UpdatedTime", false) + count, blogs := blogService.ListBlogs(userId, notebookId, page, blogPageSize, "PublicTime", false) + + c.RenderArgs["notebookId"] = notebookId + c.RenderArgs["notebook"] = notebook + c.RenderArgs["title"] = c.Message("blogClass") + " - " + notebook.Title + c.RenderArgs["blogs"] = blogs + c.RenderArgs["page"] = page + c.RenderArgs["pageSize"] = blogPageSize + c.RenderArgs["count"] = count + + return c.RenderTemplate("blog/index.html") +} + +// 显示分类的最近博客, json +func (c Blog) ListCateLatest(notebookId string) revel.Result { + if notebookId == "" { + return c.E404() + } + // 自定义域名 + hasDomain, userBlog := c.domain() + userId := "" + if hasDomain { + userId = userBlog.UserId.Hex() + } + + var notebook info.Notebook + notebook = notebookService.GetNotebookById(notebookId) + if !notebook.IsBlog { + return c.E404() + } + if userId != "" && userId != notebook.UserId.Hex() { + return c.E404() + } + userId = notebook.UserId.Hex() + + if !c.blogCommon(userId, userBlog, info.User{}) { + return c.E404() + } + + // 分页的话, 需要分页信息, totalPage, curPage + page := 1 + _, blogs := blogService.ListBlogs(userId, notebookId, page, 5, "PublicTime", false) + re := info.NewRe() + re.Ok = true + re.List = blogs + return c.RenderJson(re) +} + +func (c Blog) Index(userIdOrEmail string) revel.Result { + // 自定义域名 + hasDomain, userBlog := c.domain() + userId := "" + if hasDomain { + userId = userBlog.UserId.Hex() + } + + // 用户id为空, 转至博客平台 + if userIdOrEmail == "" { + userIdOrEmail = leanoteUserId; + } + var userInfo info.User + if userId != "" { + userInfo = userService.GetUserInfoByAny(userId) + } else { + userInfo = userService.GetUserInfoByAny(userIdOrEmail) + } + userId = userInfo.UserId.Hex() + + if !c.blogCommon(userId, userBlog, userInfo) { + return c.E404() + } + + // 分页的话, 需要分页信息, totalPage, curPage + page := c.GetPage() + count, blogs := blogService.ListBlogs(userId, "", page, blogPageSize, "PublicTime", false) c.RenderArgs["blogs"] = blogs c.RenderArgs["page"] = page c.RenderArgs["pageSize"] = blogPageSize c.RenderArgs["count"] = count - - // 当前notebook - c.RenderArgs["notebookId"] = notebookId - c.RenderArgs["notebook"] = notebook - - c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId) - - - if notebookId == "" { - c.RenderArgs["index"] = true - } - - c.getRecentBlogs(userId) + c.RenderArgs["index"] = true + c.RenderArgs["notebookId"] = "" + c.RenderArgs["title"] = userBlog.Title return c.RenderTemplate("blog/index.html") } // 详情 func (c Blog) View(noteId string) revel.Result { + // 自定义域名 + hasDomain, userBlog := c.domain() + userId := "" + if hasDomain { + userId = userBlog.UserId.Hex() + } + blog := blogService.GetBlog(noteId) - c.RenderArgs["blog"] = blog - userInfo := userService.GetUserInfo(blog.UserId.Hex()) + if userId != "" && userInfo.UserId.Hex() != userId { + return c.E404() + } + c.RenderArgs["blog"] = blog c.RenderArgs["userInfo"] = userInfo + c.RenderArgs["title"] = blog.Title + " - " + userInfo.Username - c.RenderArgs["title"] = blog.Title + " - " + userInfo.Email + userId = userInfo.UserId.Hex() + c.blogCommon(userId, userBlog, info.User{}) - userId := userInfo.UserId.Hex() - c.isMe(userId) - - c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId) - - // 得到博客设置信息 - c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId) - - c.getRecentBlogs(userId) + // 得到访问者id + visitUserId := c.GetUserId() + if(visitUserId != "") { + visitUserInfo := userService.GetUserInfo(visitUserId) + c.RenderArgs["visitUserInfoJson"] = c.Json(visitUserInfo) + c.RenderArgs["visitUserInfo"] = visitUserInfo + } else { + c.RenderArgs["visitUserInfoJson"] = "{}"; + } return c.RenderTemplate("blog/view.html") } // 搜索 -func (c Blog) SearchBlog(userId, key string) revel.Result { - c.RenderArgs["title"] = "搜索 " + key +func (c Blog) Search(userIdOrEmail, key string) revel.Result { + // 自定义域名 + hasDomain, userBlog := c.domain() + userId := "" + if hasDomain { + userId = userBlog.UserId.Hex() + } + + c.RenderArgs["title"] = c.Message("search") + " - " + key c.RenderArgs["key"] = key - userInfo := userService.GetUserInfoByAny(userId) + var userInfo info.User + if userId != "" { + userInfo = userService.GetUserInfoByAny(userId) + } else { + userInfo = userService.GetUserInfoByAny(userIdOrEmail) + } c.RenderArgs["userInfo"] = userInfo - userId = userInfo.UserId.Hex() + c.blogCommon(userId, userBlog, userInfo) page := c.GetPage() - _, blogs := blogService.SearchBlog(key, userId, page, searchBlogPageSize, "UpdatedTime", false) + _, blogs := blogService.SearchBlog(key, userId, page, searchBlogPageSize, "PublicTime", false) c.RenderArgs["blogs"] = blogs c.RenderArgs["key"] = key - c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId) - // 得到博客设置信息 - c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId) - - c.getRecentBlogs(userId) - - c.isMe(userId) - return c.RenderTemplate("blog/search.html") } @@ -162,17 +305,15 @@ func (c Blog) Set() revel.Result { userInfo := userService.GetUserInfo(userId) c.RenderArgs["userInfo"] = userInfo - c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId) - // 得到博客设置信息 - c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId) - c.RenderArgs["title"] = "博客设置" + c.RenderArgs["title"] = c.Message("blogSet") c.RenderArgs["isMe"] = true c.RenderArgs["set"] = true - c.getRecentBlogs(userId) + c.RenderArgs["allowCustomDomain"] = configService.GetGlobalStringConfig("allowCustomDomain") - c.SetLocale(); + userBlog := blogService.GetUserBlog(userId) + c.blogCommon(userId, userBlog, info.User{}) return c.RenderTemplate("blog/set.html") } @@ -194,37 +335,38 @@ func (c Blog) SetUserBlogStyle(userBlog info.UserBlogStyle) revel.Result { } // userId可能是其它的 -func (c Blog) AboutMe(userId string) revel.Result { - userInfo := userService.GetUserInfoByAny(userId) +func (c Blog) AboutMe(userIdOrEmail string) revel.Result { + // 自定义域名 + hasDomain, userBlog := c.domain() + userId := "" + if hasDomain { + userId = userBlog.UserId.Hex() + } + + var userInfo info.User + if userId != "" { + userInfo = userService.GetUserInfoByAny(userId) + } else { + userInfo = userService.GetUserInfoByAny(userIdOrEmail) + } + if userInfo.UserId == "" { return c.E404() } userId = userInfo.UserId.Hex() c.RenderArgs["userInfo"] = userInfo - - c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId) - - c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId) c.RenderArgs["aboutMe"] = true - - c.RenderArgs["title"] = "关于我" - - c.isMe(userId) - - c.getRecentBlogs(userId) + + c.RenderArgs["title"] = c.Message("aboutMe") + c.blogCommon(userId, userBlog, info.User{}) return c.RenderTemplate("blog/about_me.html") } -// 当前的博客是否是我的 -func (c Blog) isMe(userId string) { - c.RenderArgs["isMe"] = userId == c.GetUserId() -} - // 优化, 这里不要得到count func (c Blog) getRecentBlogs(userId string) { - _, c.RenderArgs["recentBlogs"] = blogService.ListBlogs(userId, "", 1, 5, "UpdatedTime", false) + _, c.RenderArgs["recentBlogs"] = blogService.ListBlogs(userId, "", 1, 5, "PublicTime", false) } // 可以不要, 因为注册的时候已经把username设为email了 @@ -233,4 +375,130 @@ func (c Blog) setRenderUserInfo(userInfo info.User) { userInfo.Username = userInfo.Email } c.RenderArgs["userInfo"] = userInfo +} + +//--------------------------- +// 后台 note<->blog + +// 设置/取消Blog; 置顶 +func (c Blog) SetNote2Blog(noteId string, isBlog, isTop bool) revel.Result { + noteUpdate := bson.M{} + if isTop { + isBlog = true + } + if !isBlog { + isTop = false + } + noteUpdate["IsBlog"] = isBlog + noteUpdate["IsTop"] = isTop + if isBlog { + noteUpdate["PublicTime"] = time.Now() + } + re := noteService.UpdateNote(c.GetUserId(), c.GetUserId(), + noteId, noteUpdate) + return c.RenderJson(re) +} + +// 设置notebook <-> blog +func (c Blog) SetNotebook2Blog(notebookId string, isBlog bool) revel.Result { + noteUpdate := bson.M{"IsBlog": isBlog} + re := notebookService.UpdateNotebook(c.GetUserId(), + notebookId, noteUpdate) + return c.RenderJson(re) +} + +//---------------- +// 社交, 点赞, 评论 + +// 我是否点过赞? +// 所有点赞的用户列表 +// 各个评论中是否我也点过赞? +func (c Blog) GetLike(noteId string) revel.Result { + userId := c.GetUserId() + + // 我也点过? + isILikeIt := blogService.IsILikeIt(noteId, userId) + // 点赞用户列表 + likedUsers, hasMoreLikedUser := blogService.ListLikedUsers(noteId, false) + + result := map[string]interface{}{} + result["isILikeIt"] = isILikeIt + result["likedUsers"] = likedUsers + result["hasMoreLikedUser"] = hasMoreLikedUser + + return c.RenderJson(result) +} +func (c Blog) GetLikeAndComments(noteId string) revel.Result { + userId := c.GetUserId() + + // 我也点过? + isILikeIt := blogService.IsILikeIt(noteId, userId) + // 点赞用户列表 + likedUsers, hasMoreLikedUser := blogService.ListLikedUsers(noteId, false) + // 评论 + page := c.GetPage() + pageInfo, comments, commentUserInfo := blogService.ListComments(userId, noteId, page, 15) + + result := map[string]interface{}{} + result["isILikeIt"] = isILikeIt + result["likedUsers"] = likedUsers + result["hasMoreLikedUser"] = hasMoreLikedUser + result["pageInfo"] = pageInfo + result["comments"] = comments + result["commentUserInfo"] = commentUserInfo + + return c.RenderJson(result) +} + +func (c Blog) IncReadNum(noteId string) revel.Result { + blogService.IncReadNum(noteId) + return nil +} +// 点赞 +func (c Blog) LikeBlog(noteId string) revel.Result { + userId := c.GetUserId() + re := info.NewRe() + re.Ok, re.Item = blogService.LikeBlog(noteId, userId) + + return c.RenderJson(re) +} +func (c Blog) ListLikes(noteId string) revel.Result { + return nil +} + +func (c Blog) ListComments(noteId string) revel.Result { + // 评论 + userId := c.GetUserId() + page := c.GetPage() + pageInfo, comments, commentUserInfo := blogService.ListComments(userId, noteId, page, 15) + + result := map[string]interface{}{} + result["pageInfo"] = pageInfo + result["comments"] = comments + result["commentUserInfo"] = commentUserInfo + + return c.RenderJson(result) +} +func (c Blog) DeleteComment(noteId, commentId string) revel.Result { + re := info.NewRe() + re.Ok = blogService.DeleteComment(noteId, commentId, c.GetUserId()) + return c.RenderJson(re) +} +func (c Blog) Comment(noteId, content, toCommentId string) revel.Result { + re := info.NewRe() + re.Ok, re.Item = blogService.Comment(noteId, toCommentId, c.GetUserId(), content); + return c.RenderJson(re) +} +func (c Blog) LikeComment(commentId string) revel.Result { + re := info.NewRe() + ok, isILikeIt, num := blogService.LikeComment(commentId, c.GetUserId()) + re.Ok = ok + re.Item = bson.M{"IsILikeIt": isILikeIt, "Num": num} + return c.RenderJson(re) +} + +func (c Blog) Report(noteId, commentId, reason string) revel.Result { + re := info.NewRe() + re.Ok = blogService.Report(noteId, commentId, reason, c.GetUserId()); + return c.RenderJson(re) } \ No newline at end of file diff --git a/app/controllers/CaptchaController.go b/app/controllers/CaptchaController.go new file mode 100644 index 0000000..a4dd621 --- /dev/null +++ b/app/controllers/CaptchaController.go @@ -0,0 +1,43 @@ +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() +} \ No newline at end of file diff --git a/app/controllers/FileController.go b/app/controllers/FileController.go index dd5cebe..e8609bd 100644 --- a/app/controllers/FileController.go +++ b/app/controllers/FileController.go @@ -5,6 +5,7 @@ import ( // "encoding/json" "gopkg.in/mgo.v2/bson" . "github.com/leanote/leanote/app/lea" + "github.com/leanote/leanote/app/lea/netutil" "github.com/leanote/leanote/app/info" "io/ioutil" "os" @@ -22,7 +23,7 @@ type File struct { func (c File) UploadBlogLogo() revel.Result { re := c.uploadImage("logo", ""); - c.RenderArgs["fileUrlPath"] = siteUrl + "/" + re.Id + c.RenderArgs["fileUrlPath"] = re.Id c.RenderArgs["resultCode"] = re.Code c.RenderArgs["resultMsg"] = re.Msg @@ -53,6 +54,24 @@ func (c File) PasteImage(noteId string) revel.Result { return c.RenderJson(re) } +// 头像设置 +func (c File) UploadAvatar() revel.Result { + re := c.uploadImage("logo", ""); + + 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); @@ -243,6 +262,38 @@ func (c File) CopyImage(userId, fileId, toUserId string) revel.Result { return c.RenderJson(re) } +// 复制外网的图片, 成公共图片 放在/upload下 +func (c File) CopyHttpImage(src string) revel.Result { + re := info.NewRe() + fileUrlPath := "upload/" + c.GetUserId() + "/images" + dir := revel.BasePath + "/public/" + 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 = fileService.AddImage(fileInfo, "", c.GetUserId()) + + return c.RenderJson(re) +} + //------------ // 过时 已弃用! func (c File) UploadImage(renderHtml string) revel.Result { diff --git a/app/controllers/IndexController.go b/app/controllers/IndexController.go index 9f39cf0..c357f66 100644 --- a/app/controllers/IndexController.go +++ b/app/controllers/IndexController.go @@ -3,7 +3,7 @@ package controllers import ( "github.com/revel/revel" "github.com/leanote/leanote/app/info" - . "github.com/leanote/leanote/app/lea" +// . "github.com/leanote/leanote/app/lea" ) // 首页 @@ -29,7 +29,7 @@ func (c Index) Suggestion(addr, suggestion string) revel.Result { // 发给我 go func() { - SendToLeanote("建议", "建议", "UserId: " + c.GetUserId() + "
Suggestions: " + suggestion) + emailService.SendEmail("leanote@leanote.com", "建议", "UserId: " + c.GetUserId() + "
Suggestions: " + suggestion) }(); return c.RenderJson(re) diff --git a/app/controllers/NoteController.go b/app/controllers/NoteController.go index 17b48c5..aca4420 100644 --- a/app/controllers/NoteController.go +++ b/app/controllers/NoteController.go @@ -5,12 +5,13 @@ import ( // "encoding/json" "gopkg.in/mgo.v2/bson" . "github.com/leanote/leanote/app/lea" - "github.com/leanote/leanote/app/lea/html2image" "github.com/leanote/leanote/app/info" -// "os" + "os/exec" // "github.com/leanote/leanote/app/types" // "io/ioutil" // "fmt" +// "bytes" +// "os" ) type Note struct { @@ -52,6 +53,8 @@ func (c Note) Index() revel.Result { // 当然, 还需要得到第一个notes的content //... + adminUsername, _ := revel.Config.String("adminUsername") + c.RenderArgs["isAdmin"] = adminUsername == userInfo.Username c.RenderArgs["userInfo"] = userInfo c.RenderArgs["userInfoJson"] = c.Json(userInfo) c.RenderArgs["notebooks"] = c.Json(notebooks) @@ -221,31 +224,131 @@ func (c Note) SearchNoteByTags(tags []string) revel.Result { return c.RenderJson(blogs) } -//----------------- +//------------------ // html2image +// 判断是否有权限生成 +// 博客也可以调用 +// 这是脚本调用, 没有cookie, 不执行权限控制, 通过传来的appKey判断 +func (c Note) ToImage(noteId, appKey string) revel.Result { + // 虽然传了cookie但是这里还是不能得到userId, 所以还是通过appKey来验证之 + appKeyTrue, _ := revel.Config.String("app.secret") + if appKeyTrue != appKey { + return c.RenderText("") + } + note := noteService.GetNoteById(noteId) + if note.NoteId == "" { + return c.RenderText("") + } + + c.SetLocale() + + noteUserId := note.UserId.Hex() + content := noteService.GetNoteContent(noteId, noteUserId) + userInfo := userService.GetUserInfo(noteUserId); + + c.RenderArgs["blog"] = note + c.RenderArgs["content"] = content.Content + c.RenderArgs["userInfo"] = userInfo + userBlog := blogService.GetUserBlog(noteUserId) + c.RenderArgs["userBlog"] = userBlog + + return c.RenderTemplate("html2Image/index.html") +} + func (c Note) Html2Image(noteId string) revel.Result { re := info.NewRe() userId := c.GetUserId() - note := noteService.GetNote(noteId, userId) + note := noteService.GetNoteById(noteId) if note.NoteId == "" { + re.Msg = "No Note" return c.RenderJson(re) } - content := noteService.GetNoteContent(noteId, userId) + noteUserId := note.UserId.Hex() + // 是否有权限 + if noteUserId != userId { + // 是否是有权限协作的 + if !note.IsBlog && !shareService.HasReadPerm(noteUserId, userId, noteId) { + re.Msg = "No Perm" + return c.RenderJson(re) + } + } + // path 判断是否需要重新生成之 - fileUrlPath := "/upload/" + userId + "/images/weibo" + fileUrlPath := "/upload/" + noteUserId + "/images/weibo" dir := revel.BasePath + "/public/" + fileUrlPath if !ClearDir(dir) { + re.Msg = "No Dir" return c.RenderJson(re) } filename := note.NoteId.Hex() + ".png"; path := dir + "/" + filename - // 生成之 - html2image.ToImage(userId, c.GetUsername(), noteId, note.Title, content.Content, path) + // cookie + cookieName := revel.CookiePrefix + "_SESSION" + cookie, err := c.Request.Cookie(cookieName) + cookieStr := cookie.String() + cookieValue := "" + if err == nil && len(cookieStr) > len(cookieName) { + cookieValue = cookieStr[len(cookieName)+1:] + } - re.Ok = true - re.Id = fileUrlPath + "/" + filename + appKey, _ := revel.Config.String("app.secret") + cookieDomain, _ := revel.Config.String("cookie.domain") + // 生成之 + url := siteUrl + "/note/toImage?noteId=" + noteId + "&appKey=" + appKey; + // /Users/life/Documents/bin/phantomjs/bin/phantomjs /Users/life/Desktop/test/b.js + binPath := configService.GetGlobalStringConfig("toImageBinPath") + if binPath == "" { + return c.RenderJson(re); + } + cc := binPath + " \"" + url + "\" \"" + path + "\" \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + cmd := exec.Command("/bin/sh", "-c", cc) + Log(cc); + b, err := cmd.Output() + if err == nil { + re.Ok = true + re.Id = fileUrlPath + "/" + filename + } else { + re.Msg = string(b) + Log("error:......") + Log(string(b)) + } + return c.RenderJson(re) + + /* + // 这里速度慢, 生成不完全(图片和内容都不全) + content := noteService.GetNoteContent(noteId, noteUserId) + userInfo := userService.GetUserInfo(noteUserId); + + c.SetLocale() + + c.RenderArgs["blog"] = note + c.RenderArgs["content"] = content.Content + c.RenderArgs["userInfo"] = userInfo + userBlog := blogService.GetUserBlog(noteUserId) + c.RenderArgs["userBlog"] = userBlog + + html := c.RenderTemplateStr("html2Image/index.html") // Result类型的 + contentFile := dir + "/html"; + fout, err := os.Create(contentFile) + if err != nil { + return c.RenderJson(re) + } + fout.WriteString(html); + fout.Close() + + cc := "/Users/life/Documents/bin/phantomjs/bin/phantomjs /Users/life/Desktop/test/c.js \"" + contentFile + "\" \"" + path + "\"" + cmd := exec.Command("/bin/sh", "-c", cc) + b, err := cmd.Output() + if err == nil { + re.Ok = true + re.Id = fileUrlPath + "/" + filename + } else { + Log(string(b)) + } + */ + } \ No newline at end of file diff --git a/app/controllers/UserController.go b/app/controllers/UserController.go index 8abe42a..c2b74b8 100644 --- a/app/controllers/UserController.go +++ b/app/controllers/UserController.go @@ -8,7 +8,7 @@ import ( "github.com/leanote/leanote/app/info" // "github.com/leanote/leanote/app/types" // "io/ioutil" - "fmt" +// "fmt" // "math" // "os" // "path" @@ -19,43 +19,48 @@ type User struct { 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 func (c User) UpdateUsername(username string) revel.Result { re := info.NewRe(); - // 判断是否满足最基本的, 4位, 不含特殊字符, 大小写无关. email大小写无关 - if len(username) < 4 { - re.Ok = false - re.Msg = "至少4位" - return c.RenderJson(re); + if(c.GetUsername() == "demo") { + re.Msg = "cannotUpdateDemo" + return c.RenderRe(re); } - if !IsUsername(username) { - re.Ok = false - re.Msg = "不能包含特殊字符" - return c.RenderJson(re); + + if re.Ok, re.Msg = Vd("username", username); !re.Ok { + return c.RenderRe(re); } re.Ok, re.Msg = userService.UpdateUsername(c.GetUserId(), username) if(re.Ok) { c.UpdateSession("Username", username) } - return c.RenderJson(re); + return c.RenderRe(re); } // 修改密码 func (c User) UpdatePwd(oldPwd, pwd string) revel.Result { re := info.NewRe(); - if oldPwd == "" { - re.Msg = "旧密码错误" - return c.RenderJson(re); + if(c.GetUsername() == "demo") { + re.Msg = "cannotUpdateDemo" + return c.RenderRe(re); } - - re.Ok, re.Msg = IsGoodPwd(pwd) - if !re.Ok { - return c.RenderJson(re); + if re.Ok, re.Msg = Vd("password", oldPwd); !re.Ok { + return c.RenderRe(re); + } + if re.Ok, re.Msg = Vd("password", pwd); !re.Ok { + return c.RenderRe(re); } - re.Ok, re.Msg = userService.UpdatePwd(c.GetUserId(), oldPwd, pwd) - return c.RenderJson(re); + return c.RenderRe(re); } // 更新主题 @@ -75,14 +80,7 @@ func (c User) SendRegisterEmail(content, toEmail string) revel.Result { return c.RenderJson(re); } - // 发送邮件 - var userInfo = c.GetUserInfo(); - siteUrl, _ := revel.Config.String("site.url") - url := siteUrl + "/register?from=" + userInfo.Username - body := fmt.Sprintf("点击链接注册leanote: %v. ", url, url); - body = content + "
" + body - re.Ok = SendEmail(toEmail, userInfo.Username + "邀请您注册leanote", "邀请注册", body) - + re.Ok = emailService.SendInviteEmail(c.GetUserInfo(), toEmail, content) return c.RenderJson(re); } @@ -91,15 +89,23 @@ func (c User) SendRegisterEmail(content, toEmail string) revel.Result { // 重新发送激活邮件 func (c User) ReSendActiveEmail() revel.Result { re := info.NewRe() - re.Ok = userService.RegisterSendActiveEmail(c.GetUserId(), c.GetEmail()) + 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) + if(c.GetUsername() == "demo") { + re.Msg = "cannotUpdateDemo" + return c.RenderJson(re); + } + if re.Ok, re.Msg = Vd("email", email); !re.Ok { + return c.RenderRe(re); + } + + re.Ok, re.Msg = emailService.UpdateEmailSendActiveEmail(c.GetUserInfo(), email) + return c.RenderRe(re) } // 通过点击链接 @@ -145,22 +151,12 @@ func (c User) ActiveEmail(token string) revel.Result { // 第三方账号添加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) + 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 = userService.ThirdAddUser(c.GetUserId(), email, pwd) @@ -169,17 +165,20 @@ func (c User) AddAccount(email, pwd string) revel.Result { c.UpdateSession("Email", email); } - return c.RenderJson(re) + return c.RenderRe(re) } //----------------- // 用户偏爱 -func (c User) UpdateColumnWidth(notebookWidth, noteListWidth int) revel.Result { +func (c User) UpdateColumnWidth(notebookWidth, noteListWidth, mdEditorWidth int) revel.Result { re := info.NewRe() - re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth) + re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth, mdEditorWidth) if re.Ok { c.UpdateSession("NotebookWidth", strconv.Itoa(notebookWidth)); - c.UpdateSession("NoteListWidth", strconv.Itoa(noteListWidth)); + c.UpdateSession("NoteListWidth", strconv.Itoa(noteListWidth)); + c.UpdateSession("MdEditorWidth", strconv.Itoa(mdEditorWidth)); + + LogJ(c.Session) } return c.RenderJson(re) } diff --git a/app/controllers/admin/AdminBaseController.go b/app/controllers/admin/AdminBaseController.go index 296cf88..6b3ab18 100644 --- a/app/controllers/admin/AdminBaseController.go +++ b/app/controllers/admin/AdminBaseController.go @@ -48,4 +48,12 @@ func (c AdminBaseController) getSorter(sorterField string, isAsc bool, okSorter } 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) + } } \ No newline at end of file diff --git a/app/controllers/admin/AdminController.go b/app/controllers/admin/AdminController.go index d4ec713..5bdc5e8 100644 --- a/app/controllers/admin/AdminController.go +++ b/app/controllers/admin/AdminController.go @@ -17,9 +17,22 @@ func (c Admin) Index() revel.Result { 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 + return c.RenderTemplate("admin/" + t + ".html") +} + func (c Admin) GetView(view string) revel.Result { return c.RenderTemplate("admin/" + view); } \ No newline at end of file diff --git a/app/controllers/admin/AdminData.go b/app/controllers/admin/AdminData.go new file mode 100644 index 0000000..8304c79 --- /dev/null +++ b/app/controllers/admin/AdminData.go @@ -0,0 +1,114 @@ +package admin + +import ( + "github.com/revel/revel" + . "github.com/leanote/leanote/app/lea" + "github.com/leanote/leanote/app/info" + "archive/tar" + "compress/gzip" + "os" + "io" + "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 +} diff --git a/app/controllers/admin/AdminEmailController.go b/app/controllers/admin/AdminEmailController.go new file mode 100644 index 0000000..b12494f --- /dev/null +++ b/app/controllers/admin/AdminEmailController.go @@ -0,0 +1,233 @@ +package admin + +import ( + "github.com/revel/revel" + . "github.com/leanote/leanote/app/lea" + "github.com/leanote/leanote/app/info" + "strings" + "strconv" +) + +// 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 := authService.Login(demoUsername, demoPassword) + 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 + "
" + 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"); +} \ No newline at end of file diff --git a/app/controllers/admin/AdminSettingController.go b/app/controllers/admin/AdminSettingController.go index 630b2a2..41d92ac 100644 --- a/app/controllers/admin/AdminSettingController.go +++ b/app/controllers/admin/AdminSettingController.go @@ -29,12 +29,22 @@ func (c AdminSetting) Blog() revel.Result { func (c AdminSetting) DoBlogTag(recommendTags, newTags string) revel.Result { re := info.NewRe() - re.Ok = configService.UpdateUserArrayConfig(c.GetUserId(), "recommendTags", strings.Split(recommendTags, ",")) - re.Ok = configService.UpdateUserArrayConfig(c.GetUserId(), "newTags", strings.Split(newTags, ",")) + 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 { @@ -51,12 +61,53 @@ func (c AdminSetting) DoDemo(demoUsername, demoPassword string) revel.Result { return c.RenderJson(re) } - re.Ok = configService.UpdateUserStringConfig(c.GetUserId(), "demoUserId", userInfo.UserId.Hex()) - re.Ok = configService.UpdateUserStringConfig(c.GetUserId(), "demoUsername", demoUsername) - re.Ok = configService.UpdateUserStringConfig(c.GetUserId(), "demoPassword", demoPassword) + 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 AdminSetting) ToImage() revel.Result { + c.RenderArgs["toImageBinPath"] = configService.GetGlobalStringConfig("toImageBinPath") + return c.RenderTemplate("admin/setting/toImage.html"); +} +func (c AdminSetting) DoToImage(toImageBinPath string) revel.Result { + re := info.NewRe() + re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "toImageBinPath", toImageBinPath) + 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) 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) +} \ No newline at end of file diff --git a/app/controllers/admin/AdminUpgradeController.go b/app/controllers/admin/AdminUpgradeController.go new file mode 100644 index 0000000..c8dc206 --- /dev/null +++ b/app/controllers/admin/AdminUpgradeController.go @@ -0,0 +1,18 @@ +package admin + +import ( + "github.com/revel/revel" +// "encoding/json" +// . "github.com/leanote/leanote/app/lea" +// "io/ioutil" +) + +// Upgrade controller +type AdminUpgrade struct { + AdminBaseController +} + +func (c AdminUpgrade) UpgradeBlog() revel.Result { + upgradeService.UpgradeBlog() + return nil; +} \ No newline at end of file diff --git a/app/controllers/admin/init.go b/app/controllers/admin/init.go index 8907670..f725029 100644 --- a/app/controllers/admin/init.go +++ b/app/controllers/admin/init.go @@ -25,6 +25,8 @@ 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 adminUsername = "admin" // 拦截器 @@ -115,12 +117,18 @@ func InitService() { 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{}) revel.OnAppStart(func() { adminUsername, _ = revel.Config.String("adminUsername") }) diff --git a/app/controllers/init.go b/app/controllers/init.go index 4f47a40..cc257ed 100644 --- a/app/controllers/init.go +++ b/app/controllers/init.go @@ -25,6 +25,8 @@ 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" @@ -47,10 +49,15 @@ var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": tru "FindPasswordUpdate": true, "Suggestion": true, }, + "Note": map[string]bool{"ToImage": true}, "Blog": map[string]bool{"Index": true, "View": true, "AboutMe": true, - "SearchBlog": true, + "Cate": true, + "Search": true, + "GetLikeAndComments": true, + "IncReadNum": true, + "ListComments": true, }, // 用户的激活与修改邮箱都不需要登录, 通过链接地址 "User": map[string]bool{"UpdateEmail": true, @@ -118,6 +125,8 @@ func InitService() { suggestionService = service.SuggestionS authService = service.AuthS configService = service.ConfigS + emailService = service.EmailS + sessionService = service.SessionS } func init() { diff --git a/app/db/Mgo.go b/app/db/Mgo.go index 66b0c7a..55c2326 100644 --- a/app/db/Mgo.go +++ b/app/db/Mgo.go @@ -40,6 +40,15 @@ 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 + +// session +var Sessions *mgo.Collection // 初始化时连接数据库 func Init() { @@ -113,12 +122,17 @@ func Init() { NoteImages = Session.DB(dbname).C("note_images") Configs = Session.DB(dbname).C("configs") -} - -func init() { - revel.OnAppStart(func() { - Init() - }) + EmailLogs = Session.DB(dbname).C("email_logs") + + // 社交 + BlogLikes = Session.DB(dbname).C("blog_likes") + BlogComments = Session.DB(dbname).C("blog_comments") + + // 举报 + Reports = Session.DB(dbname).C("reports") + + // session + Sessions = Session.DB(dbname).C("sessions") } func close() { diff --git a/app/i18n/i18n.go b/app/i18n/i18n.go index 02d8b56..8c0f77b 100644 --- a/app/i18n/i18n.go +++ b/app/i18n/i18n.go @@ -10,8 +10,8 @@ import ( // convert revel msg to js msg -var msgBasePath = "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/messages/" -var targetBasePath = "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/public/js/i18n/" +var msgBasePath = "/Users/life/Documents/Go/package1/src/github.com/leanote/leanote/messages/" +var targetBasePath = "/Users/life/Documents/Go/package1/src/github.com/leanote/leanote/public/js/i18n/" func parse(filename string) { file, err := os.Open(msgBasePath + filename) reader := bufio.NewReader(file) @@ -62,11 +62,28 @@ func parse(filename string) { if err2 != nil { file2, err2 = os.Create(targetName) } - file2.WriteString("var MSG = " + str + ";") + file2.WriteString("var MSG = " + 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; +}`) } // 生成js的i18n文件 func main() { parse("msg.en") parse("msg.zh") + parse("blog.zh") + parse("blog.en") } diff --git a/app/info/BlogInfo.go b/app/info/BlogInfo.go index fe5af1d..976c24f 100644 --- a/app/info/BlogInfo.go +++ b/app/info/BlogInfo.go @@ -2,6 +2,7 @@ package info import ( "gopkg.in/mgo.v2/bson" + "time" ) // 只为blog, 不为note @@ -10,35 +11,76 @@ type BlogItem struct { Note Content string // 可能是content的一部分, 截取. 点击more后就是整个信息了 HasMore bool // 是否是否还有 - User User // 用户信息 + User User // 用户信息 } type UserBlogBase struct { - Logo string `Logo` - Title string `Title` // 标题 - SubTitle string `SubTitle` // 副标题 - AboutMe string `AboutMe` // 关于我 + Logo string `Logo` + Title string `Title` // 标题 + SubTitle string `SubTitle` // 副标题 + AboutMe string `AboutMe` // 关于我 } type UserBlogComment struct { - CanComment bool `CanComment` // 是否可以评论 - DisqusId string `DisqusId` + CanComment bool `CanComment` // 是否可以评论 + CommentType string `CommentType` // default 或 disqus + DisqusId string `DisqusId` } type UserBlogStyle struct { - Style string `Style` // 风格 + Style string `Style` // 风格 + Css string `Css` // 自定义css } // 每个用户一份博客设置信息 type UserBlog struct { - UserId bson.ObjectId `bson:"_id"` // 谁的 - Logo string `Logo` - Title string `Title` // 标题 - SubTitle string `SubTitle` // 副标题 - AboutMe string `AboutMe` // 关于我 + UserId bson.ObjectId `bson:"_id"` // 谁的 + Logo string `Logo` + Title string `Title` // 标题 + SubTitle string `SubTitle` // 副标题 + AboutMe string `AboutMe` // 关于我 + + CanComment bool `CanComment` // 是否可以评论 - CanComment bool `CanComment` // 是否可以评论 - DisqusId string `DisqusId` - - Style string `Style` // 风格 -} \ No newline at end of file + CommentType string `CommentType` // default 或 disqus + DisqusId string `DisqusId` + + Style string `Style` // 风格 + Css string `Css` // 自定义css + + SubDomain string `SubDomain` // 二级域名 + Domain string `Domain` // 自定义域名 +} + +//------------------------ +// 社交功能, 点赞, 分享, 评论 + +// 点赞记录 +type BlogLike struct { + LikeId bson.ObjectId `bson:"_id"` + NoteId bson.ObjectId `NoteId` + UserId bson.ObjectId `UserId` + CreatedTime time.Time `CreatedTime` +} + +// 评论 +type BlogComment struct { + CommentId bson.ObjectId `bson:"_id"` + 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 +} diff --git a/app/info/Configinfo.go b/app/info/Configinfo.go index c318e13..1fbecd5 100644 --- a/app/info/Configinfo.go +++ b/app/info/Configinfo.go @@ -5,11 +5,21 @@ import ( "time" ) -// 配置 -// 用户配置高于全局配置 +// 配置, 每一个配置一行记录 type Config struct { - UserId bson.ObjectId `bson:"_id"` - StringConfigs map[string]string `StringConfigs` // key => value - ArrayConfigs map[string][]string `ArrayConfigs` // key => []value - UpdatedTime time.Time `UpdatedTime` + 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` } diff --git a/app/info/EmailLogInfo.go b/app/info/EmailLogInfo.go new file mode 100644 index 0000000..e0e98dd --- /dev/null +++ b/app/info/EmailLogInfo.go @@ -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` +} diff --git a/app/info/NoteInfo.go b/app/info/NoteInfo.go index e342cc1..e8560e0 100644 --- a/app/info/NoteInfo.go +++ b/app/info/NoteInfo.go @@ -15,21 +15,28 @@ type Note struct { Title string `Title` // 标题 Desc string `Desc` // 描述, 非html - ImgSrc string `ImgSrc` // 图片, 第一张缩略图地址 - Tags []string `Tags,omitempty` - - IsTrash bool `IsTrash` // 是否是trash, 默认是false + ImgSrc string `ImgSrc` // 图片, 第一张缩略图地址 + Tags []string `Tags,omitempty` - IsBlog bool `IsBlog,omitempty` // 是否设置成了blog 2013/12/29 新加 + IsTrash bool `IsTrash` // 是否是trash, 默认是false + + IsBlog bool `IsBlog,omitempty` // 是否设置成了blog 2013/12/29 新加 IsRecommend bool `IsRecommend,omitempty` // 是否为推荐博客 2014/9/24新加 - IsTop bool `IsTop,omitempty` // blog是否置顶 + IsTop bool `IsTop,omitempty` // blog是否置顶 + + // 2014/9/28 添加评论社交功能 + ReadNum int `ReadNum,omitempty` // 阅读次数 2014/9/28 + LikeNum int `LikeNum,omitempty` // 点赞次数 2014/9/28 + CommentNum int `CommentNum,omitempty` // 评论次数 2014/9/28 IsMarkdown bool `IsMarkdown` // 是否是markdown笔记, 默认是false - AttachNum int `AttachNum` // 2014/9/21, attachments num + AttachNum int `AttachNum` // 2014/9/21, attachments num CreatedTime time.Time `CreatedTime` UpdatedTime time.Time `UpdatedTime` + RecommendTime time.Time `RecommendTime,omitempty` // 推荐时间 + PublicTime time.Time `PublicTime,omitempty` // 发表时间, 公开为博客则设置 UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了 } diff --git a/app/info/ReportInfo.go b/app/info/ReportInfo.go new file mode 100644 index 0000000..c1e738f --- /dev/null +++ b/app/info/ReportInfo.go @@ -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` +} diff --git a/app/info/SessionInfo.go b/app/info/SessionInfo.go new file mode 100644 index 0000000..a724626 --- /dev/null +++ b/app/info/SessionInfo.go @@ -0,0 +1,19 @@ +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` // 验证码 + + CreatedTime time.Time `CreatedTime` + UpdatedTime time.Time `UpdatedTime` // 更新时间, expire这个时间会自动清空 +} diff --git a/app/info/UserInfo.go b/app/info/UserInfo.go index bf7ffb9..104069e 100644 --- a/app/info/UserInfo.go +++ b/app/info/UserInfo.go @@ -27,6 +27,7 @@ type User struct { // 用户配置 NotebookWidth int `NotebookWidth` // 笔记本宽度 NoteListWidth int `NoteListWidth` // 笔记列表宽度 + MdEditorWidth int `MdEditorWidth` // markdown 左侧编辑器宽度 LeftIsMin bool `LeftIsMin` // 左侧是否是隐藏的, 默认是打开的 // 这里 第三方登录 diff --git a/app/init.go b/app/init.go index 92125ab..10e94f9 100644 --- a/app/init.go +++ b/app/init.go @@ -4,9 +4,13 @@ import ( "github.com/revel/revel" . "github.com/leanote/leanote/app/lea" "github.com/leanote/leanote/app/service" + "github.com/leanote/leanote/app/db" "github.com/leanote/leanote/app/controllers" "github.com/leanote/leanote/app/controllers/admin" _ "github.com/leanote/leanote/app/lea/binder" + "github.com/leanote/leanote/app/lea/session" + "github.com/leanote/leanote/app/lea/memcache" + "github.com/leanote/leanote/app/lea/route" "reflect" "fmt" "html/template" @@ -14,20 +18,24 @@ import ( "strings" "strconv" "time" + "encoding/json" ) func init() { // Filters is the default set of global filters. revel.Filters = []revel.Filter{ revel.PanicFilter, // Recover from panics and display an error page instead. - RouterFilter, + route.RouterFilter, // revel.RouterFilter, // Use the routing table to select the right Action // AuthFilter, // Invoke the action. revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters. revel.ParamsFilter, // Parse parameters into Controller.Params. - revel.SessionFilter, // Restore and write the session cookie. + // 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.ValidationFilter, // Restore kept validation errors and save new ones from cookie. @@ -48,12 +56,34 @@ func init() { i = i - 1; return i } + revel.TemplateFuncs["join"] = func(arr []string) template.HTML { + if arr == nil { + return template.HTML("") + } + return template.HTML(strings.Join(arr, ",")) + } revel.TemplateFuncs["concat"] = func(s1, s2 string) template.HTML { return template.HTML(s1 + s2) } + revel.TemplateFuncs["concatStr"] = func(strs ...string) string { + str := "" + for _, s := range strs { + str += s + } + return str + } + revel.TemplateFuncs["json"] = func(i interface{}) string { + b, _ := json.Marshal(i) + return string(b) + } revel.TemplateFuncs["datetime"] = func(t time.Time) template.HTML { return template.HTML(t.Format("2006-01-02 15:04:05")) } + revel.TemplateFuncs["unixDatetime"] = func(unixSec string) template.HTML { + sec, _ := strconv.Atoi(unixSec) + t := time.Unix(int64(sec), 0) + return template.HTML(t.Format("2006-01-02 15:04:05")) + } // interface是否有该字段 revel.TemplateFuncs["has"] = func(i interface{}, key string) bool { @@ -63,6 +93,26 @@ func init() { } // tags + 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) + for i, tag := range tags { + str := revel.Message(locale, tag) + if strings.HasPrefix(str, "???") { + str = tag + } + tagStr += str + if i != lenTags - 1 { + tagStr += "," + } + } + return template.HTML(tagStr) + } + /* revel.TemplateFuncs["blogTags"] = func(tags []string) template.HTML { if tags == nil || len(tags) == 0 { return "" @@ -83,7 +133,7 @@ func init() { } return template.HTML(tagStr) } - + */ revel.TemplateFuncs["li"] = func(a string) string { Log(a) Log("life==") @@ -130,6 +180,15 @@ func init() { 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{}来接收 @@ -155,7 +214,7 @@ func init() { } // 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 { return ""; } @@ -170,11 +229,6 @@ func init() { nextPage := page + 1 var preUrl, nextUrl string - urlBase := "/blog/" + userId - if notebookId != "" { - urlBase += "/" + notebookId - } - preUrl = urlBase + "?page=" + strconv.Itoa(prePage) nextUrl = urlBase + "?page=" + strconv.Itoa(nextPage) @@ -238,8 +292,13 @@ func init() { // init Email revel.OnAppStart(func() { + // 数据库 + db.Init() + // email配置 InitEmail() - + InitVd() + memcache.InitMemcache() // session服务 + // 其它service service.InitService() controllers.InitService() admin.InitService() diff --git a/app/lea/Email.go b/app/lea/Email.go index 26cf001..7f5812c 100644 --- a/app/lea/Email.go +++ b/app/lea/Email.go @@ -22,7 +22,7 @@ func InitEmail() { var bodyTpl = ` -
+
@@ -56,7 +56,7 @@ var bodyTpl = ` ` -func SendEmail(to, subject, title, body string) bool { +func SendEmailOld(to, subject, body string) bool { hp := strings.Split(host, ":") auth := smtp.PlainAuth("", username, password, hp[0]) @@ -69,9 +69,8 @@ func SendEmail(to, subject, title, body string) bool { content_type = "Content-Type: text/plain" + "; charset=UTF-8" } - // 登录之 - body = strings.Replace(bodyTpl, "$body", body, 1) - body = strings.Replace(body, "$title", title, 1) + //body = strings.Replace(bodyTpl, "$body", body, 1) + //body = strings.Replace(body, "$title", title, 1) msg := []byte("To: " + to + "\r\nFrom: " + username + "<"+ username +">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body) send_to := strings.Split(to, ";") @@ -84,7 +83,7 @@ func SendEmail(to, subject, title, body string) bool { return true } -func SendToLeanote(subject, title, body string) { +func SendToLeanoteOld(subject, title, body string) { to := "leanote@leanote.com" - SendEmail(to, subject, title, body); + SendEmailOld(to, subject, body); } \ No newline at end of file diff --git a/app/lea/File.go b/app/lea/File.go index 503f165..d3d54f9 100644 --- a/app/lea/File.go +++ b/app/lea/File.go @@ -77,4 +77,14 @@ func CopyFile(srcName, dstName string) (written int64, err error) { } defer dst.Close() return io.Copy(dst, src) +} + +func IsDirExists(path string) bool { + fi, err := os.Stat(path) + if err != nil { + return os.IsExist(err) + }else{ + return fi.IsDir() + } + return false } \ No newline at end of file diff --git a/app/lea/Vd.go b/app/lea/Vd.go new file mode 100644 index 0000000..4adcf12 --- /dev/null +++ b/app/lea/Vd.go @@ -0,0 +1,145 @@ +package lea + +import ( + "encoding/json" + "strconv" + "regexp" +) + +// 验证 + +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"} + ] +} +` +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 + }, + + "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 +} diff --git a/app/lea/captcha/Captcha.go b/app/lea/captcha/Captcha.go new file mode 100644 index 0000000..6348d2a --- /dev/null +++ b/app/lea/captcha/Captcha.go @@ -0,0 +1,399 @@ +package captcha + +import ( + "image" + "image/color" + "image/png" + "io" + "math/rand" + crand "crypto/rand" + "time" + "strconv" +) +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 +} \ No newline at end of file diff --git a/app/lea/html2image/ToImage.go b/app/lea/html2image/ToImage.go new file mode 100644 index 0000000..e6730f7 --- /dev/null +++ b/app/lea/html2image/ToImage.go @@ -0,0 +1,10 @@ +package html2image + +import ( + "github.com/leanote/leanote/app/info" +) + +func Html2Image(userInfo info.User, note info.Note, content, toPath string) bool { + return true +} + diff --git a/app/lea/memcache/Memcache.go b/app/lea/memcache/Memcache.go index b83844c..23ab0d5 100644 --- a/app/lea/memcache/Memcache.go +++ b/app/lea/memcache/Memcache.go @@ -3,9 +3,20 @@ package memcache import ( "github.com/robfig/gomemcache/memcache" "encoding/json" + "strconv" ) -func Set(key string, value map[string]string, expiration int32) { +var client *memcache.Client + +// onAppStart后调用 +func InitMemcache() { + client = memcache.New("localhost:11211") +} + +//------------ +// map + +func SetMap(key string, value map[string]string, expiration int32) { // 把value转成byte bytes, _ := json.Marshal(value) if expiration == -1 { @@ -14,7 +25,7 @@ func Set(key string, value map[string]string, expiration int32) { client.Set(&memcache.Item{Key: key, Value: bytes, Expiration: expiration}) } -func Get(key string) map[string]string { +func GetMap(key string) map[string]string { item, err := client.Get(key) if err != nil { return nil @@ -23,4 +34,33 @@ func Get(key string) map[string]string { m := map[string]string{} json.Unmarshal(item.Value, &m) return m -} \ No newline at end of file +} + +//------------ +// string +func GetString(key string) string { + item, err := client.Get(key) + if err != nil { + return "" + } + return string(item.Value) +} +func SetString(key string, value string, expiration int32) { + if expiration == -1 { + expiration = 30 * 24 * 60 * 60 // 30天 + } + client.Set(&memcache.Item{Key: key, Value: []byte(value), Expiration: expiration}) +} + +//------------------------- +// int, 是通过转成string来存的 + +func GetInt(key string) int { + str := GetString(key) + i, _ := strconv.Atoi(str) + return i +} +func SetInt(key string, value int, expiration int32) { + str := strconv.Itoa(value) + SetString(key, str, expiration) +} diff --git a/app/lea/memcache/init.go b/app/lea/memcache/init.go deleted file mode 100644 index cca3cf7..0000000 --- a/app/lea/memcache/init.go +++ /dev/null @@ -1,11 +0,0 @@ -package memcache - -import ( - "github.com/robfig/gomemcache/memcache" -) - -var client *memcache.Client - -func init() { - // client = memcache.New("localhost:11211") -} \ No newline at end of file diff --git a/app/lea/netutil/NetUtil.go b/app/lea/netutil/NetUtil.go index 21b156c..9cf4ff3 100644 --- a/app/lea/netutil/NetUtil.go +++ b/app/lea/netutil/NetUtil.go @@ -13,7 +13,7 @@ import ( // toPath 文件保存的目录 // 默认是/tmp // 返回文件的完整目录 -func WriteUrl(url string, toPath string) (path string, ok bool) { +func WriteUrl(url string, toPath string) (length int64, newFilename, path string, ok bool) { if url == "" { return; } @@ -22,6 +22,8 @@ func WriteUrl(url string, toPath string) (path string, ok bool) { return; } + length = int64(len(content)) + // a.html?a=a11&xxx url = trimQueryParams(url) _, ext := SplitFilename(url) @@ -29,13 +31,8 @@ func WriteUrl(url string, toPath string) (path string, ok bool) { toPath = "/tmp" } // dir := filepath.Dir(toPath) - newFilename := NewGuid() + ext + newFilename = NewGuid() + ext fullPath := toPath + "/" + newFilename - /* - if err := os.MkdirAll(dir, 0777); err != nil { - return - } - */ // 写到文件中 file, err := os.Create(fullPath) @@ -54,6 +51,7 @@ func WriteUrl(url string, toPath string) (path string, ok bool) { func GetContent(url string) (content []byte, err error) { var resp *http.Response resp, err = http.Get(url) + Log(err) if(resp != nil && resp.Body != nil) { defer resp.Body.Close() } else { @@ -65,6 +63,7 @@ func GetContent(url string) (content []byte, err error) { var buf []byte buf, err = ioutil.ReadAll(resp.Body) if(err != nil) { + Log(err) return } diff --git a/app/lea/Route.go b/app/lea/route/Route.go similarity index 60% rename from app/lea/Route.go rename to app/lea/route/Route.go index 4681cbb..386d74b 100644 --- a/app/lea/Route.go +++ b/app/lea/route/Route.go @@ -1,14 +1,20 @@ -package lea +package route import ( "github.com/revel/revel" +// "github.com/leanote/leanote/app/service" +// . "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 { @@ -24,12 +30,25 @@ func RouterFilter(c *revel.Controller, fc []revel.Filter) { //---------- // life start - path := c.Request.Request.URL.Path - // Log(c.Request.Request.URL.Host) - if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "api") { - route.ControllerName = "Api" + route.ControllerName + /* + 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" { + // api设置 + // leanote.com/api/user/get => ApiUser::Get + if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "api/") { + route.ControllerName = "Api" + route.ControllerName + } + // end } - // end // Set the action. if err := c.SetAction(route.ControllerName, route.MethodName); err != nil { diff --git a/app/lea/session/MSession.go b/app/lea/session/MSession.go new file mode 100644 index 0000000..808ed58 --- /dev/null +++ b/app/lea/session/MSession.go @@ -0,0 +1,38 @@ +package session + +import ( + "github.com/revel/revel" + "github.com/leanote/leanote/app/lea/memcache" + . "github.com/leanote/leanote/app/lea" +) + +// 使用filter +// 很巧妙就使用了memcache来处理session +// revel的session(cookie)只存sessionId, 其它信息存在memcache中 + +func MSessionFilter(c *revel.Controller, fc []revel.Filter) { + sessionId := c.Session.Id() + + // 从memcache中得到cache, 赋给session + cache := revel.Session(memcache.GetMap(sessionId)) + + Log("memcache") + LogJ(cache) + if cache == nil { + cache = revel.Session{} + cache.Id() + } + c.Session = cache + + // Make session vars available in templates as {{.session.xyz}} + c.RenderArgs["session"] = c.Session + + fc[0](c, fc[1:]) + + // 再把session保存之 + LogJ(c.Session) + memcache.SetMap(sessionId, c.Session, -1) + + // 只留下sessionId + c.Session = revel.Session{revel.SESSION_ID_KEY: sessionId} +} \ No newline at end of file diff --git a/app/lea/session/session.go b/app/lea/session/session.go index a5990a1..6cc0cea 100644 --- a/app/lea/session/session.go +++ b/app/lea/session/session.go @@ -1,31 +1,208 @@ package session import ( - "github.com/robfig/revel" - "github.com/leanote/leanote/app/lea/memcache" -// . "leanote/app/lea" + "github.com/revel/revel" +// . "github.com/leanote/leanote/app/lea" + "crypto/rand" + "encoding/hex" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "time" ) -// 使用filter -// 很巧妙就使用了memcache来处理session -// revel的session(cookie)只存sessionId, 其它信息存在memcache中 +// 主要修改revel的cookie, 设置Domain +// 为了使sub domain共享cookie +// cookie.domain = leanote.com -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() +// A signed cookie (and thus limited to 4kb in size). +// Restriction: Keys may not have a colon in them. +type Session map[string]string + +const ( + SESSION_ID_KEY = "_ID" + TIMESTAMP_KEY = "_TS" +) + +// expireAfterDuration is the time to live, in seconds, of a session cookie. +// It may be specified in config as "session.expires". Values greater than 0 +// set a persistent cookie with a time to live as specified, and the value 0 +// sets a session cookie. +var expireAfterDuration time.Duration +var cookieDomain = "" // life +func init() { + // Set expireAfterDuration, default to 30 days if no value in config + revel.OnAppStart(func() { + var err error + if expiresString, ok := revel.Config.String("session.expires"); !ok { + expireAfterDuration = 30 * 24 * time.Hour + } else if expiresString == "session" { + expireAfterDuration = 0 + } else if expireAfterDuration, err = time.ParseDuration(expiresString); err != nil { + panic(fmt.Errorf("session.expires invalid: %s", err)) + } + + cookieDomain, _ = revel.Config.String("cookie.domain") + }) +} + +// Id retrieves from the cookie or creates a time-based UUID identifying this +// session. +func (s Session) Id() string { + if sessionIdStr, ok := s[SESSION_ID_KEY]; ok { + return sessionIdStr } - c.Session = cache + + buffer := make([]byte, 32) + if _, err := rand.Read(buffer); err != nil { + panic(err) + } + + s[SESSION_ID_KEY] = hex.EncodeToString(buffer) + return s[SESSION_ID_KEY] +} + +// getExpiration return a time.Time with the session's expiration date. +// If previous session has set to "session", remain it +func (s Session) getExpiration() time.Time { + if expireAfterDuration == 0 || s[TIMESTAMP_KEY] == "session" { + // Expire after closing browser + return time.Time{} + } + return time.Now().Add(expireAfterDuration) +} + +// cookie returns an http.Cookie containing the signed session. +func (s Session) cookie() *http.Cookie { + var sessionValue string + ts := s.getExpiration() + s[TIMESTAMP_KEY] = getSessionExpirationCookie(ts) + for key, value := range s { + if strings.ContainsAny(key, ":\x00") { + panic("Session keys may not have colons or null bytes") + } + if strings.Contains(value, "\x00") { + panic("Session values may not have null bytes") + } + sessionValue += "\x00" + key + ":" + value + "\x00" + } + + sessionData := url.QueryEscape(sessionValue) + cookie := http.Cookie{ + Name: revel.CookiePrefix + "_SESSION", + Value: revel.Sign(sessionData) + "-" + sessionData, + Path: "/", + HttpOnly: revel.CookieHttpOnly, + Secure: revel.CookieSecure, + Expires: ts.UTC(), + } + + if cookieDomain != "" { + cookie.Domain = cookieDomain + } + + return &cookie +} + +// sessionTimeoutExpiredOrMissing returns a boolean of whether the session +// cookie is either not present or present but beyond its time to live; i.e., +// whether there is not a valid session. +func sessionTimeoutExpiredOrMissing(session Session) bool { + if exp, present := session[TIMESTAMP_KEY]; !present { + return true + } else if exp == "session" { + return false + } else if expInt, _ := strconv.Atoi(exp); int64(expInt) < time.Now().Unix() { + return true + } + return false +} + +// getSessionFromCookie returns a Session struct pulled from the signed +// session cookie. +func getSessionFromCookie(cookie *http.Cookie) Session { + session := make(Session) + + // Separate the data from the signature. + hyphen := strings.Index(cookie.Value, "-") + if hyphen == -1 || hyphen >= len(cookie.Value)-1 { + return session + } + sig, data := cookie.Value[:hyphen], cookie.Value[hyphen+1:] + + // Verify the signature. + if !revel.Verify(data, sig) { + revel.INFO.Println("Session cookie signature failed") + return session + } + + revel.ParseKeyValueCookie(data, func(key, val string) { + session[key] = val + }) + + if sessionTimeoutExpiredOrMissing(session) { + session = make(Session) + } + + return session +} + +// SessionFilter is a Revel Filter that retrieves and sets the session cookie. +// Within Revel, it is available as a Session attribute on Controller instances. +// The name of the Session cookie is set as CookiePrefix + "_SESSION". +func SessionFilter(c *revel.Controller, fc []revel.Filter) { + session := restoreSession(c.Request.Request) + // c.Session, 重新生成一个revel.Session给controller!!! +// Log("sessoin--------") +// LogJ(session) + revelSession := revel.Session(session) // 强制转换 还是同一个对象, 但有个问题, 这样Session.Id()方法是用revel的了 + c.Session = revelSession + // 生成sessionId + c.Session.Id() + sessionWasEmpty := len(c.Session) == 0 + + // Make session vars available in templates as {{.session.xyz}} + c.RenderArgs["session"] = c.Session fc[0](c, fc[1:]) - - // 再把session保存之 - memcache.Set(sessionId, c.Session, -1) - - // 只留下sessionId - c.Session = revel.Session{revel.SESSION_ID_KEY: sessionId} + + // Store the signed session if it could have changed. + if len(c.Session) > 0 || !sessionWasEmpty { + // 转换成lea.Session + session = Session(c.Session) + c.SetCookie(session.cookie()) + } +} + +// restoreSession returns either the current session, retrieved from the +// session cookie, or a new session. +func restoreSession(req *http.Request) Session { + cookie, err := req.Cookie(revel.CookiePrefix + "_SESSION") + if err != nil { + return make(Session) + } else { + return getSessionFromCookie(cookie) + } +} + +// getSessionExpirationCookie retrieves the cookie's time to live as a +// string of either the number of seconds, for a persistent cookie, or +// "session". +func getSessionExpirationCookie(t time.Time) string { + if t.IsZero() { + return "session" + } + return strconv.FormatInt(t.Unix(), 10) +} + +// SetNoExpiration sets session to expire when browser session ends +func (s Session) SetNoExpiration() { + s[TIMESTAMP_KEY] = "session" +} + +// SetDefaultExpiration sets session to expire after default duration +func (s Session) SetDefaultExpiration() { + delete(s, TIMESTAMP_KEY) } \ No newline at end of file diff --git a/app/release/release.go b/app/release/release.go index 17c027a..05c0d2b 100644 --- a/app/release/release.go +++ b/app/release/release.go @@ -39,6 +39,7 @@ var cmdPath = "/usr/local/bin/uglifyjs" func cmdError(err error) { if err != nil { + fmt.Println(err) fmt.Fprintf(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, "", "") } else { fmt.Println("OK") @@ -63,6 +64,7 @@ func combineJs() { for _, js := range jss { to := base + js + "-min.js" + fmt.Println(to) compressJs(js) // 每个压缩后的文件放入之 @@ -85,6 +87,7 @@ func dev() { "notebook.js": "notebook-min.js", "share.js": "share-min.js", "tag.js": "tag-min.js", + "main.js": "main-min.js", "jquery.contextmenu.js": "jquery.contextmenu-min.js", "editor/editor.js": "editor/editor-min.js", "/public/mdeditor/editor/scrollLink.js": "/public/mdeditor/editor/scrollLink-min.js", @@ -108,7 +111,8 @@ func tinymce() { // cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify", "bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]") cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify") cmd.Dir = "/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master" - _, err := cmd.CombinedOutput() + c, err := cmd.CombinedOutput() + fmt.Println(string(c)) cmdError(err) } @@ -116,11 +120,12 @@ func main() { dev(); // 其它零散的需要压缩的js - otherJss := []string{"tinymce/tinymce", "js/app/page", "js/contextmenu/jquery.contextmenu", + otherJss := []string{"tinymce/tinymce", "js/main", "js/app/page", "js/contextmenu/jquery.contextmenu", "mdeditor/editor/scrollLink", "mdeditor/editor/editor", "mdeditor/editor/jquery.waitforimages", "mdeditor/editor/pagedown/local/Markdown.local.zh", + "mdeditor/editor/pagedown/local/Markdown.local.en", "mdeditor/editor/pagedown/Markdown.Editor", "mdeditor/editor/pagedown/Markdown.Sanitizer", "mdeditor/editor/pagedown/Markdown.Converter", diff --git a/app/service/AuthService.go b/app/service/AuthService.go index f4c3928..1d4e66a 100644 --- a/app/service/AuthService.go +++ b/app/service/AuthService.go @@ -4,9 +4,10 @@ import ( "gopkg.in/mgo.v2/bson" // "github.com/leanote/leanote/app/db" "github.com/leanote/leanote/app/info" - "github.com/revel/revel" +// "github.com/revel/revel" . "github.com/leanote/leanote/app/lea" "fmt" + "strconv" ) // 登录与权限 @@ -16,7 +17,8 @@ type AuthService struct { // pwd已md5了 func (this *AuthService) Login(emailOrUsername, pwd string) info.User { - return userService.LoginGetUserInfo(emailOrUsername, Md5(pwd)) + userInfo := userService.LoginGetUserInfo(emailOrUsername, Md5(pwd)) + return userInfo } // 注册 @@ -56,20 +58,30 @@ func (this *AuthService) register(user info.User) (bool, string) { email := user.Email // 添加leanote -> 该用户的共享 - leanoteUserId, _ := revel.Config.String("register.sharedUserId"); // "5368c1aa99c37b029d000001"; - nk1, _ := revel.Config.String("register.sharedUserShareNotebookId"); // 5368c1aa99c37b029d000002" // leanote - welcomeNoteId, _ := revel.Config.String("register.welcomeNoteId") // "5368c1b919807a6f95000000" // 欢迎来到leanote - - if leanoteUserId != "" && nk1 != "" && welcomeNoteId != "" { - shareService.AddShareNotebook(nk1, 0, leanoteUserId, email); - shareService.AddShareNote(welcomeNoteId, 0, leanoteUserId, email); + registerSharedUserId := configService.GetGlobalStringConfig("registerSharedUserId") + if(registerSharedUserId != "") { + registerSharedNotebooks := configService.GetGlobalArrMapConfig("registerSharedNotebooks") + registerSharedNotes := configService.GetGlobalArrMapConfig("registerSharedNotes") + registerCopyNoteIds := configService.GetGlobalArrayConfig("registerCopyNoteIds") - // 将welcome copy给我 - note := noteService.CopySharedNote(welcomeNoteId, title2Id["life"].Hex(), leanoteUserId, user.UserId.Hex()); + // 添加共享笔记本 + for _, notebook := range registerSharedNotebooks { + perm, _ := strconv.Atoi(notebook["perm"]) + shareService.AddShareNotebook(notebook["notebookId"], perm, registerSharedUserId, email); + } - // 公开为博客 - noteUpdate := bson.M{"IsBlog": true} - noteService.UpdateNote(user.UserId.Hex(), user.UserId.Hex(), note.NoteId.Hex(), noteUpdate) + // 添加共享笔记 + for _, note := range registerSharedNotes { + perm, _ := strconv.Atoi(note["perm"]) + shareService.AddShareNote(note["noteId"], perm, registerSharedUserId, email); + } + + // 复制笔记 + for _, noteId := range registerCopyNoteIds { + note := noteService.CopySharedNote(noteId, title2Id["life"].Hex(), registerSharedUserId, user.UserId.Hex()); + noteUpdate := bson.M{"IsBlog": true} + noteService.UpdateNote(user.UserId.Hex(), user.UserId.Hex(), note.NoteId.Hex(), noteUpdate) + } } //--------------- diff --git a/app/service/BlogService.go b/app/service/BlogService.go index 39a7a78..4ca78fd 100644 --- a/app/service/BlogService.go +++ b/app/service/BlogService.go @@ -3,10 +3,12 @@ package service import ( "github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/db" -// . "github.com/leanote/leanote/app/lea" + . "github.com/leanote/leanote/app/lea" "gopkg.in/mgo.v2/bson" // "time" // "sort" + "strings" + "time" ) // blog @@ -207,15 +209,25 @@ func (this *BlogService) ListAllBlogs(tag string, keywords string, isRecommend b //------------------------ // 博客设置 +func (this *BlogService) fixUserBlog(userBlog *info.UserBlog) { + /* + if userBlog.Title == "" { + userInfo := userService.GetUserInfo(userBlog) + userBlog.Title = userInfo.Username + " 's Blog" + } + */ + + // Logo路径问题, 有些有http: 有些没有 + Log(userBlog.Logo) + if userBlog.Logo != "" && !strings.HasPrefix(userBlog.Logo, "http") { + userBlog.Logo = strings.Trim(userBlog.Logo, "/") + userBlog.Logo = siteUrl + "/" + userBlog.Logo + } +} func (this *BlogService) GetUserBlog(userId string) info.UserBlog { userBlog := info.UserBlog{} db.Get(db.UserBlogs, userId, &userBlog) - - if userBlog.Title == "" { - userInfo := userService.GetUserInfo(userId) - userBlog.Title = userInfo.Username + " 的博客" - } - + this.fixUserBlog(&userBlog) return userBlog } @@ -225,7 +237,8 @@ func (this *BlogService) UpdateUserBlog(userBlog info.UserBlog) bool { } // 修改之UserBlogBase func (this *BlogService) UpdateUserBlogBase(userId string, userBlog info.UserBlogBase) bool { - return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog) + ok := db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog) + return ok } func (this *BlogService) UpdateUserBlogComment(userId string, userBlog info.UserBlogComment) bool { return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog) @@ -234,10 +247,325 @@ func (this *BlogService) UpdateUserBlogStyle(userId string, userBlog info.UserBl return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog) } -//------------ + +//--------------------- // 后台管理 // 推荐博客 func (this *BlogService) SetRecommend(noteId string, isRecommend bool) bool { - return db.UpdateByQField(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId), "IsBlog": true}, "IsRecommend", isRecommend) + data := bson.M{"IsRecommend": isRecommend} + if isRecommend { + data["RecommendTime"] = time.Now() + } + return db.UpdateByQMap(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId), "IsBlog": true}, data) +} + +//---------------------- +// 博客社交, 评论 + +// 返回所有liked用户, bool是否还有 +func (this *BlogService) ListLikedUsers(noteId string, isAll bool) ([]info.User, bool) { + // 默认前5 + pageSize := 5 + skipNum, sortFieldR := parsePageAndSort(1, pageSize, "CreatedTime", false) + + likes := []info.BlogLike{} + query := bson.M{"NoteId": bson.ObjectIdHex(noteId)} + q := db.BlogLikes.Find(query); + + // 总记录数 + count, _ := q.Count() + if count == 0 { + return nil, false + } + + if isAll { + q.Sort(sortFieldR).Skip(skipNum).Limit(pageSize).All(&likes) + } else { + q.Sort(sortFieldR).All(&likes) + } + + // 得到所有userIds + userIds := make([]bson.ObjectId, len(likes)) + for i, like := range likes { + userIds[i] = like.UserId + } + // 得到用户信息 + userMap := userService.MapUserInfoAndBlogInfosByUserIds(userIds) + + users := make([]info.User, len(likes)); + for i, like := range likes { + users[i] = userMap[like.UserId] + } + + return users, count > pageSize +} + +func (this *BlogService) IsILikeIt(noteId, userId string) bool { + if userId == "" { + return false + } + if db.Has(db.BlogLikes, bson.M{"NoteId": bson.ObjectIdHex(noteId), "UserId": bson.ObjectIdHex(userId)}) { + return true + } + return false +} + +// 阅读次数统计+1 +func (this *BlogService) IncReadNum(noteId string) bool { + note := noteService.GetNoteById(noteId) + if note.IsBlog { + return db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"ReadNum": 1}}) + } + return false +} + +// 点赞 +// retun ok , isLike +func (this *BlogService) LikeBlog(noteId, userId string) (ok bool, isLike bool) { + ok = false + isLike = false + if noteId == "" || userId == "" { + return + } + // 判断是否点过赞, 如果点过那么取消点赞 + note := noteService.GetNoteById(noteId) + if !note.IsBlog /*|| note.UserId.Hex() == userId */{ + return + } + + noteIdO := bson.ObjectIdHex(noteId) + userIdO := bson.ObjectIdHex(userId) + var n int + if !db.Has(db.BlogLikes, bson.M{"NoteId": noteIdO, "UserId": userIdO}) { + n = 1 + // 添加之 + db.Insert(db.BlogLikes, info.BlogLike{LikeId: bson.NewObjectId(), NoteId: noteIdO, UserId: userIdO, CreatedTime: time.Now()}) + isLike = true + } else { + // 已点过, 那么删除之 + n = -1 + db.Delete(db.BlogLikes, bson.M{"NoteId": noteIdO, "UserId": userIdO}) + isLike = false + } + ok = db.Update(db.Notes, bson.M{"_id": noteIdO}, bson.M{"$inc": bson.M{"LikeNum": n}}) + + return +} + +// 评论 +// 在noteId博客下userId 给toUserId评论content +// commentId可为空(针对某条评论评论) +func (this *BlogService) Comment(noteId, toCommentId, userId, content string) (bool, info.BlogComment) { + var comment info.BlogComment + if content == "" { + return false, comment + } + + note := noteService.GetNoteById(noteId) + if !note.IsBlog { + return false, comment + } + + comment = info.BlogComment{CommentId: bson.NewObjectId(), + NoteId: bson.ObjectIdHex(noteId), + UserId: bson.ObjectIdHex(userId), + Content: content, + CreatedTime: time.Now(), + } + var comment2 = info.BlogComment{} + if toCommentId != "" { + comment2 = info.BlogComment{} + db.Get(db.BlogComments, toCommentId, &comment2) + if comment2.CommentId != "" { + comment.ToCommentId = comment2.CommentId + comment.ToUserId = comment2.UserId + } + } else { + // comment.ToUserId = note.UserId + } + ok := db.Insert(db.BlogComments, comment) + if ok { + // 评论+1 + db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"CommentNum": 1}}) + } + + if userId != note.UserId.Hex() || toCommentId != "" { + go func() { + this.sendEmail(note, comment2, userId, content); + }() + } + + return ok, comment +} + +// 发送email +func (this *BlogService) sendEmail(note info.Note, comment info.BlogComment, userId, content string) { + emailService.SendCommentEmail(note, comment, userId, content); + /* + 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 += " 的评论"; + } + + body := "{header}评论内容:
" + content + "
"; + href := "http://"+ configService.GetBlogDomain() + "/view/" + note.NoteId.Hex() + body += "
博客链接: " + href + "{footer}"; + + emailService.SendEmail(toUserInfo.Email, subject, body) + */ +} + +// 作者(或管理员)可以删除所有评论 +// 自己可以删除评论 +func (this *BlogService) DeleteComment(noteId, commentId, userId string) bool { + note := noteService.GetNoteById(noteId) + if !note.IsBlog { + return false + } + + comment := info.BlogComment{} + db.Get(db.BlogComments, commentId, &comment) + + if comment.CommentId == "" { + return false + } + + if userId == adminUserId || note.UserId.Hex() == userId || comment.UserId.Hex() == userId { + if db.Delete(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)}) { + // 评论-1 + db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"CommentNum": -1}}) + return true + } + } + + return false +} + +// 点赞/取消赞 +func (this *BlogService) LikeComment(commentId, userId string) (ok bool, isILike bool, num int) { + ok = false + isILike = false + num = 0 + comment := info.BlogComment{} + + db.Get(db.BlogComments, commentId, &comment) + + var n int + if comment.LikeUserIds != nil && len(comment.LikeUserIds) > 0 && InArray(comment.LikeUserIds, userId) { + n = -1 + // 从点赞名单删除 + db.Update(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)}, + bson.M{"$pull": bson.M{"LikeUserIds": userId}}) + isILike = false + } else { + n = 1 + // 添加之 + db.Update(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)}, + bson.M{"$push": bson.M{"LikeUserIds": userId}}) + isILike = true + } + + if comment.LikeUserIds == nil { + num = 0 + } else { + num = len(comment.LikeUserIds) + n + } + + ok = db.Update(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)}, + bson.M{"$set": bson.M{"LikeNum": num}}) + + return +} + +// 评论列表 +// userId主要是显示userId是否点过某评论的赞 +// 还要获取用户信息 +func (this *BlogService) ListComments(userId, noteId string, page, pageSize int) (info.Page, []info.BlogCommentPublic, map[string]info.User) { + pageInfo := info.Page{CurPage: page} + + comments2 := []info.BlogComment{} + + skipNum, sortFieldR := parsePageAndSort(page, pageSize, "CreatedTime", false) + + query := bson.M{"NoteId": bson.ObjectIdHex(noteId)} + q := db.BlogComments.Find(query); + + // 总记录数 + count, _ := q.Count() + q.Sort(sortFieldR).Skip(skipNum).Limit(pageSize).All(&comments2) + + if(len(comments2) == 0) { + return pageInfo, nil, nil + } + + comments := make([]info.BlogCommentPublic, len(comments2)) + // 我是否点过赞呢? + for i, comment := range comments2 { + comments[i].BlogComment = comment + if comment.LikeNum > 0 && comment.LikeUserIds != nil && len(comment.LikeUserIds) > 0 && InArray(comment.LikeUserIds, userId) { + comments[i].IsILikeIt = true + } + } + + note := noteService.GetNoteById(noteId); + + // 得到用户信息 + userIdsMap := map[bson.ObjectId]bool{note.UserId: true} + for _, comment := range comments { + userIdsMap[comment.UserId] = true + if comment.ToUserId != "" { // 可能为空 + userIdsMap[comment.ToUserId] = true + } + } + userIds := make([]bson.ObjectId, len(userIdsMap)) + i := 0 + for userId, _ := range userIdsMap { + userIds[i] = userId + i++ + } + + // 得到用户信息 + userMap := userService.MapUserInfoByUserIds(userIds) + userMap2 := make(map[string]info.User, len(userMap)) + for userId, v := range userMap { + userMap2[userId.Hex()] = v + } + + pageInfo = info.NewPage(page, pageSize, count, nil) + + return pageInfo, comments, userMap2 +} + +// 举报 +func (this *BlogService) Report(noteId, commentId, reason, userId string) (bool) { + note := noteService.GetNoteById(noteId) + if !note.IsBlog { + return false + } + + report := info.Report{ReportId: bson.NewObjectId(), + NoteId: bson.ObjectIdHex(noteId), + UserId: bson.ObjectIdHex(userId), + Reason: reason, + CreatedTime: time.Now(), + } + if commentId != "" { + report.CommentId = bson.ObjectIdHex(commentId) + } + return db.Insert(db.Reports, report) } \ No newline at end of file diff --git a/app/service/ConfigService.go b/app/service/ConfigService.go index 54a90d5..45cd594 100644 --- a/app/service/ConfigService.go +++ b/app/service/ConfigService.go @@ -2,40 +2,38 @@ package service import ( "github.com/leanote/leanote/app/info" -// . "github.com/leanote/leanote/app/lea" + . "github.com/leanote/leanote/app/lea" "github.com/leanote/leanote/app/db" "gopkg.in/mgo.v2/bson" "github.com/revel/revel" "time" + "os" + "os/exec" + "fmt" + "strings" + "strconv" ) // 配置服务 +// 只是全局的, 用户的配置没有 type ConfigService struct { // 全局的 + GlobalAllConfigs map[string]interface{} GlobalStringConfigs map[string]string GlobalArrayConfigs map[string][]string - - // 两种配置, 用户自己的 - UserStringConfigs map[string]string - UserArrayConfigs map[string][]string - - // 合并之后的 - AllStringConfigs map[string]string - AllArrayConfigs map[string][]string + GlobalMapConfigs map[string]map[string]string + GlobalArrMapConfigs map[string][]map[string]string } var adminUserId = "" // appStart时 将全局的配置从数据库中得到作为全局 func (this *ConfigService) InitGlobalConfigs() bool { + this.GlobalAllConfigs = map[string]interface{}{} this.GlobalStringConfigs = map[string]string{} this.GlobalArrayConfigs = map[string][]string{} - - this.UserStringConfigs = map[string]string{} - this.UserArrayConfigs = map[string][]string{} - - this.AllStringConfigs = map[string]string{} - this.AllArrayConfigs = map[string][]string{} + this.GlobalMapConfigs = map[string]map[string]string{} + this.GlobalArrMapConfigs = map[string][]map[string]string{} adminUsername, _ := revel.Config.String("adminUsername") if adminUsername == "" { @@ -48,84 +46,95 @@ func (this *ConfigService) InitGlobalConfigs() bool { } adminUserId = userInfo.UserId.Hex() - configs := info.Config{} - db.Get2(db.Configs, userInfo.UserId, &configs) + configs := []info.Config{} + db.ListByQ(db.Configs, bson.M{"UserId": userInfo.UserId}, &configs) - if configs.UserId == "" { - db.Insert(db.Configs, info.Config{UserId: userInfo.UserId, StringConfigs: map[string]string{}, ArrayConfigs: map[string][]string{}}) - } - - this.GlobalStringConfigs = configs.StringConfigs; - this.GlobalArrayConfigs = configs.ArrayConfigs; - - // 复制到所有配置上 - for key, value := range this.GlobalStringConfigs { - this.AllStringConfigs[key] = value - } - for key, value := range this.GlobalArrayConfigs { - this.AllArrayConfigs[key] = value + 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 + } } return true } -// 用户登录后获取用户自定义的配置, 并将所有的配置都用上 -func (this *ConfigService) InitUserConfigs(userId string) bool { - configs := info.Config{} - db.Get(db.Configs, userId, &configs) - - if configs.UserId == "" { - db.Insert(db.Configs, info.Config{UserId: bson.ObjectIdHex(userId), StringConfigs: map[string]string{}, ArrayConfigs: map[string][]string{}}) +// 通用方法 +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) } - - this.UserStringConfigs = configs.StringConfigs; - this.UserArrayConfigs = configs.ArrayConfigs; - - // 合并配置 - for key, value := range this.UserStringConfigs { - this.AllStringConfigs[key] = value - } - for key, value := range this.UserArrayConfigs { - this.AllArrayConfigs[key] = value - } - - return true -} - -// 获取配置 -func (this *ConfigService) GetStringConfig(key string) string { - return this.AllStringConfigs[key] -} -func (this *ConfigService) GetArrayConfig(key string) []string { - arr := this.AllArrayConfigs[key] - if arr == nil { - return []string{} - } - return arr } // 更新用户配置 -func (this *ConfigService) UpdateUserStringConfig(userId, key string, value string) bool { - this.UserStringConfigs[key] = value - this.AllStringConfigs[key] = value - if userId == adminUserId { - this.GlobalStringConfigs[key] = value - } - - // 保存到数据库中 - return db.UpdateByQMap(db.Configs, bson.M{"_id": bson.ObjectIdHex(userId)}, - bson.M{"StringConfigs": this.UserStringConfigs, "UpdatedTime": time.Now()}) +func (this *ConfigService) UpdateGlobalStringConfig(userId, key string, value string) bool { + return this.updateGlobalConfig(userId, key, value, false, false, false) } -func (this *ConfigService) UpdateUserArrayConfig(userId, key string, value []string) bool { - this.UserArrayConfigs[key] = value - this.AllArrayConfigs[key] = value - if userId == adminUserId { - this.GlobalArrayConfigs[key] = value - } - - // 保存到数据库中 - return db.UpdateByQMap(db.Configs, bson.M{"_id": bson.ObjectIdHex(userId)}, - bson.M{"ArrayConfigs": this.UserArrayConfigs, "UpdatedTime": time.Now()}) +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) } // 获取全局配置, 博客平台使用 @@ -138,4 +147,391 @@ func (this *ConfigService) GetGlobalArrayConfig(key string) []string { return []string{} } return arr -} \ No newline at end of file +} +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) 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(adminUserId, "registerSharedUserId", "") + return + } else { + user := userService.GetUserInfo(registerSharedUserId) + if user.UserId == "" { + ok = false + msg = "no such user: " + registerSharedUserId + return + } else { + this.UpdateGlobalStringConfig(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(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(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(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(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 + "/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 += " --directoryperdb " + 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(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(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() { + 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://" + } + }) +} + + +func (this *ConfigService) GetSchema() string { + return schema; +} +// 默认 +func (this *ConfigService) GetDefaultDomain() string { + return defaultDomain +} +// 包含http:// +func (this *ConfigService) GetDefaultUrl() string { + return schema + defaultDomain +} +// note +func (this *ConfigService) GetNoteDomain() string { + subDomain := this.GetGlobalStringConfig("noteSubDomain"); + if subDomain != "" { + return subDomain + port + } + return this.GetDefaultDomain() + "/note" +} +func (this *ConfigService) GetNoteUrl() string { + return schema + this.GetNoteDomain(); +} + +// blog +func (this *ConfigService) GetBlogDomain() string { + subDomain := this.GetGlobalStringConfig("blogSubDomain"); + if subDomain != "" { + return subDomain + port + } + return this.GetDefaultDomain() + "/blog" +} +func (this *ConfigService) GetBlogUrl() string { + return schema + this.GetBlogDomain(); +} +// lea +func (this *ConfigService) GetLeaDomain() string { + subDomain := this.GetGlobalStringConfig("leaSubDomain"); + if subDomain != "" { + return subDomain + port + } + return this.GetDefaultDomain() + "/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 +} diff --git a/app/service/EmailService.go b/app/service/EmailService.go new file mode 100644 index 0000000..09c7045 --- /dev/null +++ b/app/service/EmailService.go @@ -0,0 +1,474 @@ +package service + +import ( + "github.com/leanote/leanote/app/info" + "github.com/leanote/leanote/app/db" + . "github.com/leanote/leanote/app/lea" + "gopkg.in/mgo.v2/bson" + "time" + "strings" + "net/smtp" + "strconv" + "fmt" + "html/template" + "bytes" +) + +// 发送邮件 + +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 := siteUrl + "/user/activeEmail?token=" + token + // {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username} + token2Value := map[string]interface{}{"siteUrl": siteUrl, "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 := siteUrl + "/user/updateEmail?token=" + token + // {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.userId} {user.email} {user.username} + token2Value := map[string]interface{}{"siteUrl": siteUrl, "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 := siteUrl + "/findPassword/" + token + // {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username} + token2Value := map[string]interface{}{"siteUrl": siteUrl, "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": siteUrl, + "registerUrl": siteUrl + "/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": siteUrl, "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"] = siteUrl; + + // 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 +} \ No newline at end of file diff --git a/app/service/NoteService.go b/app/service/NoteService.go index 7fc2504..dd8386b 100644 --- a/app/service/NoteService.go +++ b/app/service/NoteService.go @@ -126,13 +126,21 @@ func (this *NoteService) AddNote(note info.Note) info.Note { note.UpdatedUserId = note.UserId // 设为blog - note.IsBlog = notebookService.IsBlog(note.NotebookId.Hex()) + notebookId := note.NotebookId.Hex() + note.IsBlog = notebookService.IsBlog(notebookId) + + if note.IsBlog { + note.PublicTime = note.UpdatedTime + } db.Insert(db.Notes, note) // tag1 tagService.AddTags(note.UserId.Hex(), note.Tags) + // recount notebooks' notes number + notebookService.ReCountNotebookNumberNotes(notebookId) + return note } @@ -276,6 +284,9 @@ func (this *NoteService) UpdateTags(noteId string, userId string, tags []string) // 2. 要判断之前是否是blog, 如果不是, 那么notebook是否是blog? func (this *NoteService) MoveNote(noteId, notebookId, userId string) info.Note { if notebookService.IsMyNotebook(notebookId, userId) { + note := this.GetNote(noteId, userId) + preNotebookId := note.NotebookId.Hex() + re := db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": false, "NotebookId": bson.ObjectIdHex(notebookId)}}) @@ -283,6 +294,13 @@ func (this *NoteService) MoveNote(noteId, notebookId, userId string) info.Note { if re { // 更新blog状态 this.updateToNotebookBlog(noteId, notebookId, userId) + + // recount notebooks' notes number + notebookService.ReCountNotebookNumberNotes(notebookId) + // 之前不是trash才统计, trash本不在统计中的 + if !note.IsTrash && preNotebookId != notebookId { + notebookService.ReCountNotebookNumberNotes(preNotebookId) + } } return this.GetNote(noteId, userId); @@ -330,7 +348,11 @@ func (this *NoteService) CopyNote(noteId, notebookId, userId string) info.Note { // 更新blog状态 isBlog := this.updateToNotebookBlog(note.NoteId.Hex(), notebookId, userId) + // recount + notebookService.ReCountNotebookNumberNotes(notebookId) + note.IsBlog = isBlog + return note } @@ -340,7 +362,7 @@ func (this *NoteService) CopyNote(noteId, notebookId, userId string) info.Note { // 复制别人的共享笔记给我 // 将别人可用的图片转为我的图片, 复制图片 func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId string) info.Note { - Log(shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId)) + // Log(shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId)) // 判断是否共享了给我 if notebookService.IsMyNotebook(notebookId, myUserId) && (shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId)) { @@ -375,6 +397,9 @@ func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId // 更新blog状态 isBlog := this.updateToNotebookBlog(note.NoteId.Hex(), notebookId, myUserId) + // recount + notebookService.ReCountNotebookNumberNotes(notebookId) + note.IsBlog = isBlog return note } @@ -482,4 +507,13 @@ func (this *NoteService) SearchNoteByTags(tags []string, userId string, pageNumb Limit(pageSize). All(¬es) return +} + +//------------ +// 统计 +func (this *NoteService) CountNote() int { + return db.Count(db.Notes, bson.M{"IsTrash": false}) +} +func (this *NoteService) CountBlog() int { + return db.Count(db.Notes, bson.M{"IsBlog": true, "IsTrash": false}) } \ No newline at end of file diff --git a/app/service/NotebookService.go b/app/service/NotebookService.go index a00931c..a1a3774 100644 --- a/app/service/NotebookService.go +++ b/app/service/NotebookService.go @@ -5,7 +5,7 @@ import ( "gopkg.in/mgo.v2/bson" "github.com/leanote/leanote/app/db" "github.com/leanote/leanote/app/info" -// . "github.com/leanote/leanote/app/lea" + . "github.com/leanote/leanote/app/lea" "sort" "time" ) @@ -96,6 +96,11 @@ func (this *NotebookService) GetNotebook(notebookId, userId string) info.Noteboo db.GetByIdAndUserId(db.Notebooks, notebookId, userId, ¬ebook) return notebook } +func (this *NotebookService) GetNotebookById(notebookId string) info.Notebook { + notebook := info.Notebook{} + db.Get(db.Notebooks, notebookId, ¬ebook) + return notebook +} // 得到用户下所有的notebook // 排序好之后返回 @@ -168,11 +173,15 @@ func (this *NotebookService) UpdateNotebook(userId, notebookId string, needUpdat // 如果有IsBlog之类的, 需要特殊处理 if isBlog, ok := needUpdate["IsBlog"]; ok { - // 设为blog/取消 + // 设为blog/取消, 把它下面所有的note都设为isBlog if is, ok2 := isBlog.(bool); ok2 { q := bson.M{"UserId": bson.ObjectIdHex(userId), "NotebookId": bson.ObjectIdHex(notebookId)} - db.UpdateByQMap(db.Notes, q, bson.M{"IsBlog": is}) + data := bson.M{"IsBlog": is} + if is { + data["PublicTime"] = time.Now() + } + db.UpdateByQMap(db.Notes, q, data) // noteContents也更新, 这个就麻烦了, noteContents表没有NotebookId // 先查该notebook下所有notes, 得到id @@ -248,4 +257,27 @@ func (this *NotebookService) DragNotebooks(userId string, curNotebookId string, } return true -} \ No newline at end of file +} + +// 重新统计笔记本下的笔记数目 +// noteSevice: AddNote, CopyNote, CopySharedNote, MoveNote +// trashService: DeleteNote (recove不用, 都统一在MoveNote里了) +func (this *NotebookService) ReCountNotebookNumberNotes(notebookId string) bool { + notebookIdO := bson.ObjectIdHex(notebookId) + count := db.Count(db.Notes, bson.M{"NotebookId": notebookIdO, "IsTrash": false}) + Log(count) + Log(notebookId) + return db.UpdateByQField(db.Notebooks, bson.M{"_id": notebookIdO}, "NumberNotes", count) +} + +func (this *NotebookService) ReCountAll() { + /* + // 得到所有笔记本 + notebooks := []info.Notebook{} + db.ListByQWithFields(db.Notebooks, bson.M{}, []string{"NotebookId"}, ¬ebooks) + + for _, each := range notebooks { + this.ReCountNotebookNumberNotes(each.NotebookId.Hex()) + } + */ +} diff --git a/app/service/PwdService.go b/app/service/PwdService.go index 3e3f052..715b1a7 100644 --- a/app/service/PwdService.go +++ b/app/service/PwdService.go @@ -2,11 +2,9 @@ package service import ( "gopkg.in/mgo.v2/bson" - "github.com/revel/revel" "github.com/leanote/leanote/app/db" "github.com/leanote/leanote/app/info" . "github.com/leanote/leanote/app/lea" - "fmt" ) // 找回密码 @@ -32,14 +30,7 @@ func (this *PwdService) FindPwd(email string) (ok bool, msg string) { } // 发送邮件 - siteUrl, _ := revel.Config.String("site.url") - url := siteUrl + "/findPassword/" + token - body := fmt.Sprintf("请点击链接修改密码: %v. %v小时后过期.", url, url, int(overHours)); - if !SendEmail(email, "leanote-找回密码", "找回密码", body) { - return false, "邮箱发送失败" - } - - ok = true + ok, msg = emailService.FindPwdSendEmail(token, email) return } diff --git a/app/service/SessionService.go b/app/service/SessionService.go new file mode 100644 index 0000000..6156790 --- /dev/null +++ b/app/service/SessionService.go @@ -0,0 +1,71 @@ +package service + +import ( + "github.com/leanote/leanote/app/info" + "github.com/leanote/leanote/app/db" + . "github.com/leanote/leanote/app/lea" + "gopkg.in/mgo.v2/bson" + "time" +// "strings" +) + +// Session存储到mongodb中 +type SessionService struct { +} + +func (this *SessionService) Update(sessionId, key string, value interface{}) bool { + return db.UpdateByQMap(db.Sessions, bson.M{"SessionId": sessionId}, + bson.M{key: value, "UpdatedTime": time.Now()}) +} +// 注销时清空session +func (this *SessionService) Clear(sessionId string) bool { + return db.Delete(db.Sessions, bson.M{"SessionId": sessionId}) +} +func (this *SessionService) Get(sessionId string) info.Session { + session := info.Session{} + db.GetByQ(db.Sessions, bson.M{"SessionId": sessionId}, &session) + + // 如果没有session, 那么插入一条之 + if session.Id == "" { + session.Id = bson.NewObjectId() + session.SessionId = sessionId + session.CreatedTime = time.Now() + session.UpdatedTime = session.CreatedTime + db.Insert(db.Sessions, session) + } + + return session +} + +//------------------ +// 错误次数处理 + +// 登录错误时间是否已超过了 +func (this *SessionService) LoginTimesIsOver(sessionId string) bool { + session := this.Get(sessionId) + return session.LoginTimes > 5 +} +// 登录成功后清空错误次数 +func (this *SessionService) ClearLoginTimes(sessionId string) bool { + return this.Update(sessionId, "LoginTimes", 0) +} +// 增加错误次数 +func (this *SessionService) IncrLoginTimes(sessionId string) bool { + session := this.Get(sessionId) + return this.Update(sessionId, "LoginTimes", session.LoginTimes + 1) +} + +//---------- +// 验证码 +func (this *SessionService) GetCaptcha(sessionId string) string { + session := this.Get(sessionId) + return session.Captcha +} +func (this *SessionService) SetCaptcha(sessionId, captcha string) bool { + this.Get(sessionId) + Log(sessionId) + Log(captcha) + ok := this.Update(sessionId, "Captcha", captcha) + Log(ok) + return ok +} diff --git a/app/service/TrashService.go b/app/service/TrashService.go index 014e6ac..f92d756 100644 --- a/app/service/TrashService.go +++ b/app/service/TrashService.go @@ -28,7 +28,13 @@ func (this *TrashService) DeleteNote(noteId, userId string) bool { // 首先删除其共享 if shareService.DeleteShareNoteAll(noteId, userId) { // 更新note isTrash = true - return db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": true}}) + if db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": true}}) { + // recount notebooks' notes number + notebookIdO := noteService.GetNotebookId(noteId) + notebookId := notebookIdO.Hex() + notebookService.ReCountNotebookNumberNotes(notebookId) + return true + } } return false } diff --git a/app/service/UpgradeService.go b/app/service/UpgradeService.go new file mode 100644 index 0000000..0f67b69 --- /dev/null +++ b/app/service/UpgradeService.go @@ -0,0 +1,27 @@ +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" +) + + +type UpgradeService struct { +} + +// 添加了PublicTime, RecommendTime +func (this *UpgradeService) UpgradeBlog() bool { + notes := []info.Note{} + db.ListByQ(db.Notes, bson.M{"IsBlog": true}, ¬es) + + // PublicTime, RecommendTime = UpdatedTime + for _, note := range notes { + db.UpdateByIdAndUserIdMap2(db.Notes, note.NoteId, note.UserId, bson.M{"PublicTime": note.UpdatedTime, "RecommendTime": note.UpdatedTime}) + Log(note.NoteId.Hex()) + } + + return true +} \ No newline at end of file diff --git a/app/service/UserService.go b/app/service/UserService.go index 6bd59c3..760ef65 100644 --- a/app/service/UserService.go +++ b/app/service/UserService.go @@ -1,20 +1,17 @@ package service import ( - "github.com/revel/revel" "github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/db" . "github.com/leanote/leanote/app/lea" "gopkg.in/mgo.v2/bson" "time" "strings" - "fmt" ) type UserService struct { } - // 添加用户 func (this *UserService) AddUser(user info.User) bool { if user.UserId == "" { @@ -27,7 +24,9 @@ func (this *UserService) AddUser(user info.User) bool { // 发送验证邮箱 go func() { - this.RegisterSendActiveEmail(user.UserId.Hex(), user.Email) + emailService.RegisterSendActiveEmail(user, user.Email) + // 发送给我 life@leanote.com + emailService.SendEmail("life@leanote.com", "新增用户", "{header}用户名" + user.Email + "{footer}"); }(); } @@ -69,10 +68,23 @@ func (this *UserService) GetUserInfoByAny(idEmailUsername string) info.User { return this.GetUserInfoByUsername(idEmailUsername) } +func (this *UserService) setUserLogo(user *info.User) { + // Logo路径问题, 有些有http: 有些没有 + if user.Logo == "" { + user.Logo = "images/blog/default_avatar.png" + } + if user.Logo != "" && !strings.HasPrefix(user.Logo, "http") { + user.Logo = strings.Trim(user.Logo, "/") + user.Logo = siteUrl + "/" + user.Logo + } +} + // 得到用户信息 userId func (this *UserService) GetUserInfo(userId string) info.User { user := info.User{} db.Get(db.Users, userId, &user) + // Logo路径问题, 有些有http: 有些没有 + this.setUserLogo(&user) return user } // 得到用户信息 email @@ -99,29 +111,27 @@ func (this *UserService) ListUserInfosByUserIds(userIds []bson.ObjectId) []info. db.ListByQ(db.Users, bson.M{"_id": bson.M{"$in": userIds}}, &users) return users } -// 用户信息和博客设置信息 -func (this *UserService) MapUserInfoAndBlogInfosByUserIds(userIds []bson.ObjectId) map[bson.ObjectId]info.User { +func (this *UserService) ListUserInfosByEmails(emails []string) []info.User { + users := []info.User{} + db.ListByQ(db.Users, bson.M{"Email": bson.M{"$in": emails}}, &users) + return users +} +// 用户信息即可 +func (this *UserService) MapUserInfoByUserIds(userIds []bson.ObjectId) map[bson.ObjectId]info.User { users := []info.User{} db.ListByQ(db.Users, bson.M{"_id": bson.M{"$in": userIds}}, &users) - userBlogs := []info.UserBlog{} - db.ListByQWithFields(db.UserBlogs, bson.M{"_id": bson.M{"$in": userIds}}, []string{"Logo"}, &userBlogs) - - userBlogMap := make(map[bson.ObjectId]info.UserBlog, len(userBlogs)) - for _, user := range userBlogs { - userBlogMap[user.UserId] = user - } - userMap := make(map[bson.ObjectId]info.User, len(users)) for _, user := range users { - if userBlog, ok := userBlogMap[user.UserId]; ok { - user.Logo = userBlog.Logo - } + this.setUserLogo(&user) userMap[user.UserId] = user } - return userMap } +// 用户信息和博客设置信息 +func (this *UserService) MapUserInfoAndBlogInfosByUserIds(userIds []bson.ObjectId) map[bson.ObjectId]info.User { + return this.MapUserInfoByUserIds(userIds) +} // 通过ids得到users, 按id的顺序组织users func (this *UserService) GetUserInfosOrderBySeq(userIds []bson.ObjectId) []info.User { @@ -174,6 +184,12 @@ func (this *UserService) UpdateUsername(userId, username string) (bool, string) return ok, "" } +// 修改头像 +func (this *UserService) UpdateAvatar(userId, avatarPath string) (bool) { + userIdO := bson.ObjectIdHex(userId) + return db.UpdateByQField(db.Users, bson.M{"_id": userIdO}, "Logo", avatarPath) +} + //---------------------- // 已经登录了的用户修改密码 func (this *UserService) UpdatePwd(userId, oldPwd, pwd string) (bool, string) { @@ -194,59 +210,6 @@ func (this *UserService) UpdateTheme(userId, theme string) (bool) { //--------------- // 修改email -// 发送激活邮件 - -// AddUser调用 -// 可以使用一个goroutine -func (this *UserService) RegisterSendActiveEmail(userId string, email string) bool { - token := tokenService.NewToken(userId, email, info.TokenActiveEmail) - - if token == "" { - return false - } - - // 发送邮件 - siteUrl, _ := revel.Config.String("site.url") - url := siteUrl + "/user/activeEmail?token=" + token - body := fmt.Sprintf("请点击链接验证邮箱: %v. %v小时后过期.", url, url, tokenService.GetOverHours(info.TokenActiveEmail)); - if !SendEmail(email, "leanote-验证邮箱", "验证邮箱", body) { - return false - } - - // 发送给我 life@leanote.com - SendEmail("life@leanote.com", "新增用户", "新增用户", "用户名" + email); - - return true -} - -// 修改邮箱 -func (this *UserService) UpdateEmailSendActiveEmail(userId, email string) (ok bool, msg string) { - // 先验证该email是否被注册了 - if userService.IsExistsUser(email) { - ok = false - msg = "该邮箱已注册" - return - } - - token := tokenService.NewToken(userId, email, info.TokenUpdateEmail) - - if token == "" { - return - } - - // 发送邮件 - siteUrl, _ := revel.Config.String("site.url") - url := siteUrl + "/user/updateEmail?token=" + token - body := "邮箱验证后您的登录邮箱为: " + email + "
"; - body += fmt.Sprintf("请点击链接验证邮箱: %v. %v小时后过期.", url, url, tokenService.GetOverHours(info.TokenUpdateEmail)); - if !SendEmail(email, "leanote-验证邮箱", "验证邮箱", body) { - msg = "发送失败, 该邮箱存在?" - return - } - ok = true - return -} - // 注册后验证邮箱 func (this *UserService) ActiveEmail(token string) (ok bool, msg, email string) { tokenInfo := info.Token{} @@ -308,13 +271,13 @@ func (this *UserService) ThirdAddUser(userId, email, pwd string) (ok bool, msg s return } - //------------ // 偏好设置 // 宽度 -func (this *UserService)UpdateColumnWidth(userId string, notebookWidth, noteListWidth int) bool { - return db.UpdateByQMap(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, bson.M{"NotebookWidth": notebookWidth, "NoteListWidth": noteListWidth}) +func (this *UserService)UpdateColumnWidth(userId string, notebookWidth, noteListWidth, mdEditorWidth int) bool { + return db.UpdateByQMap(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, + bson.M{"NotebookWidth": notebookWidth, "NoteListWidth": noteListWidth, "mdEditorWidth": mdEditorWidth}) } // 左侧是否隐藏 func (this *UserService)UpdateLeftIsMin(userId string, leftIsMin bool) bool { @@ -340,4 +303,48 @@ func (this *UserService) ListUsers(pageNumber, pageSize int, sortField string, i All(&users) page = info.NewPage(pageNumber, pageSize, count, nil) return -} \ No newline at end of file +} + +func (this *UserService) GetAllUserByFilter(userFilterEmail, userFilterWhiteList, userFilterBlackList string, verified bool) []info.User { + query := bson.M{} + + if verified { + query["Verified"] = true + } + + orQ := []bson.M{} + if userFilterEmail != "" { + orQ = append(orQ, bson.M{"Email": bson.M{"$regex": bson.RegEx{".*?" + userFilterEmail + ".*", "i"}}}, + bson.M{"Username": bson.M{"$regex": bson.RegEx{".*?" + userFilterEmail + ".*", "i"}}}, + ) + } + if(userFilterWhiteList != "") { + userFilterWhiteList = strings.Replace(userFilterWhiteList, "\r", "", -1) + emails := strings.Split(userFilterWhiteList, "\n"); + orQ = append(orQ, bson.M{"Email": bson.M{"$in": emails}}) + } + if len(orQ) > 0 { + query["$or"] = orQ + } + + emailQ := bson.M{} + if(userFilterBlackList != "") { + userFilterWhiteList = strings.Replace(userFilterBlackList, "\r", "", -1) + bEmails := strings.Split(userFilterBlackList, "\n"); + emailQ["$nin"] = bEmails + query["Email"] = emailQ + } + + LogJ(query) + users := []info.User{} + q := db.Users.Find(query); + q.All(&users) + Log(len(users)) + + return users +} + +// 统计 +func (this *UserService) CountUser() int { + return db.Count(db.Users, bson.M{}) +} diff --git a/app/service/init.go b/app/service/init.go index c9ea2c3..01fdbe9 100644 --- a/app/service/init.go +++ b/app/service/init.go @@ -1,7 +1,7 @@ package service import ( - + "github.com/revel/revel" ) // init service, for share service bettween services @@ -24,7 +24,12 @@ var attachService, AttachS *AttachService var configService, ConfigS *ConfigService var PwdS *PwdService var SuggestionS *SuggestionService +var emailService, EmailS *EmailService var AuthS *AuthService +var UpgradeS *UpgradeService +var SessionS, sessionService *SessionService + +var siteUrl string // onAppStart调用 func InitService() { @@ -45,6 +50,9 @@ func InitService() { PwdS = &PwdService{} SuggestionS = &SuggestionService{} AuthS = &AuthService{} + EmailS = NewEmailService() + UpgradeS = &UpgradeService{} + SessionS = &SessionService{} notebookService = NotebookS noteService = NoteS @@ -60,4 +68,9 @@ func InitService() { albumService = AlbumS attachService = AttachS configService = ConfigS + emailService = EmailS + sessionService = SessionS + + // + siteUrl, _ = revel.Config.String("site.url") } \ No newline at end of file diff --git a/app/test/TestNoteService.go b/app/test/TestNoteService.go index 3d3e15f..2412fa6 100644 --- a/app/test/TestNoteService.go +++ b/app/test/TestNoteService.go @@ -189,7 +189,8 @@ func testLea() { func main() { revel.BasePath = "/Users/life/Documents/Go/package/src/leanote" - testLea(); + // testLea(); + // a, b := SplitFilename("http://ab/c/a.gif#??") // println(a) // println(b) diff --git a/app/views/Admin/Blog/list.html b/app/views/Admin/Blog/list.html index ba7e19c..6f8969b 100644 --- a/app/views/Admin/Blog/list.html +++ b/app/views/Admin/Blog/list.html @@ -4,22 +4,11 @@
- +
@@ -62,16 +51,7 @@ - - isRecommend - - - - - - + @@ -82,8 +62,6 @@ - - @@ -100,21 +78,9 @@ {{.User.Username}} - - - {{.CreatedTime|datetime}} - - Send Email - {{end}} @@ -123,22 +89,11 @@
diff --git a/app/views/Admin/Setting/blog.html b/app/views/Admin/Data/configuration.html similarity index 56% rename from app/views/Admin/Setting/blog.html rename to app/views/Admin/Data/configuration.html index b2d03f1..32ef469 100644 --- a/app/views/Admin/Setting/blog.html +++ b/app/views/Admin/Data/configuration.html @@ -1,21 +1,21 @@ {{template "admin/top.html" .}} -

Blog

+

Mongodb Tool Configuration

-
+
- - - Split by ',' + + + Please input the bin mongodump's absolute path
- - - Split by ',' + + + Please input the bin mongorestore's absolute path
@@ -32,14 +32,14 @@ + +{{template "admin/end.html" .}} diff --git a/app/views/Admin/Email/emailDialog.html b/app/views/Admin/Email/emailDialog.html new file mode 100644 index 0000000..e50bb75 --- /dev/null +++ b/app/views/Admin/Email/emailDialog.html @@ -0,0 +1,76 @@ +
+ +
+ +
+
Email
+
+
+ + + input email line by line +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+ + \ No newline at end of file diff --git a/app/views/Admin/Email/list.html b/app/views/Admin/Email/list.html new file mode 100644 index 0000000..2a853ad --- /dev/null +++ b/app/views/Admin/Email/list.html @@ -0,0 +1,191 @@ +{{template "admin/top.html" .}} +

Email Logs

+ +
+
+
+ + +
+
+ +
+
+
+ + + + +
+
+
+
+ + + + + {{$url := urlConcat "/adminEmail/list" "keywords" .keywords}} + + + + + + + + + + {{range .emails}} + + + + + + + + + + {{end}} + +
+ + + Email + + + + + + + Subject + + + + + + + Ok + + + + + + + Msg + + + + + + + Date + + + + + + +
+ + + {{.Email}} + + {{.Subject}} + + {{.Ok}} + + {{.Msg}} + + {{.CreatedTime|datetime}} + + Send + Delete +
+
+
+
+ +
+ {{set . "url" (urlConcat "/adminEmail/list" "sorter" .sorter "keywords" .keywords)}} + {{template "admin/user/page.html" .}} +
+
+
+
+ +{{template "admin/footer.html" .}} + + + +{{template "admin/end.html" .}} diff --git a/app/views/Admin/Email/page.html b/app/views/Admin/Email/page.html new file mode 100644 index 0000000..68b46b8 --- /dev/null +++ b/app/views/Admin/Email/page.html @@ -0,0 +1,33 @@ +{{if gt .pageInfo.TotalPage 1}} + +{{end}} \ No newline at end of file diff --git a/app/views/Admin/Email/send.html b/app/views/Admin/Email/send.html new file mode 100644 index 0000000..a30dce2 --- /dev/null +++ b/app/views/Admin/Email/send.html @@ -0,0 +1,85 @@ +{{template "admin/top.html" .}} +

Send Email

+ +
+ +
+
+
+
Email
+
+
+ + + input email line by line +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+
+
+
+ +
+ +{{template "admin/footer.html" .}} + + + +{{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/Admin/Email/sendToUsers.html b/app/views/Admin/Email/sendToUsers.html new file mode 100644 index 0000000..fadd42b --- /dev/null +++ b/app/views/Admin/Email/sendToUsers.html @@ -0,0 +1,105 @@ +{{template "admin/top.html" .}} +

Send Email to Users

+ +
+ +
+
+
+
User filter
+
+
+ + +
+ +
+ +
+ +
+ + + input email line by line +
+ +
+ + + input email line by line +
+
+
+ +
+
Email
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+
+
+
+ +
+ +{{template "admin/footer.html" .}} + + + +{{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/Admin/Email/set.html b/app/views/Admin/Email/set.html new file mode 100644 index 0000000..011c963 --- /dev/null +++ b/app/views/Admin/Email/set.html @@ -0,0 +1,62 @@ +{{template "admin/top.html" .}} +

Email Configuration

+ +
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+ +
+ +
+
+
+
+ +
+ +{{template "admin/footer.html" .}} + + + +{{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/Admin/Email/template.html b/app/views/Admin/Email/template.html new file mode 100644 index 0000000..5d85473 --- /dev/null +++ b/app/views/Admin/Email/template.html @@ -0,0 +1,325 @@ +{{template "admin/top.html" .}} +

Email Template

+ + + +
+
+
+ +
+
+
+
+
+
+
+
+ Layout +
+ Available tokens: + $.subject + $.siteUrl +
+
+ + +
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+ Register Welcome And Email Validation: +
+ Available tokens: + header + footer + $.siteUrl + $.tokenUrl + $.token + $.tokenTimeout + $.user.userId + $.user.email + $.user.username +
+
+ + +
+ +
+ + +
+
+ Preview +
+
+
+
+
+
+
+
+
+
+
+
+
+ Update Email and Send Active Email +
+ Available tokens: + header + footer + $.siteUrl + $.tokenUrl + $.token + $.tokenTimeout + $.user.userId + $.user.email + $.user.username +
+ +
+ + +
+ +
+ + +
+ +
+ Preview +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ Find Passord +
+ Available tokens: + header + footer + $.siteUrl + $.tokenUrl + $.token + $.tokenTimeout +
+ +
+ + +
+ +
+ + +
+ +
+ Preview +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ Invite Register +
+ Available tokens: + header + footer + $.siteUrl + $.registerUrl + $.user.username + $.user.email + $.content +
+ +
+ + +
+ +
+ + +
+ +
+ Preview +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ Blog Comment +
+ Available tokens: + header + footer + $.siteUrl + $.blogUrl + +
+ $.commentContent + +
+ $.blog.id + $.blog.title + $.blog.url + +
+ $.commentUser.userId + $.commentUser.username + $.commentUser.email + $.commentUser.isBlogAuthor + +
+ $.commentedUser.userId + $.commentedUser.username + $.commentedUser.email + $.commentedUser.isBlogAuthor +
+ +
+ + +
+ +
+ + +
+ +
+ Preview +
+
+
+
+
+
+
+
+ +
+
+
+ +
+ +
+ +
+ + +{{template "admin/footer.html" .}} + + + +{{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/Admin/Setting/demo.html b/app/views/Admin/Setting/demo.html index 7672c9d..e4ec913 100644 --- a/app/views/Admin/Setting/demo.html +++ b/app/views/Admin/Setting/demo.html @@ -9,11 +9,11 @@
- +
- +
diff --git a/app/views/Admin/Setting/shareNote.html b/app/views/Admin/Setting/shareNote.html new file mode 100644 index 0000000..f90adf7 --- /dev/null +++ b/app/views/Admin/Setting/shareNote.html @@ -0,0 +1,173 @@ +{{template "admin/top.html" .}} +

Register Share Note

+ +
+ +
+
+
+
+
+ + +
+
+ +
+ {{range $notebook := .arrMap.registerSharedNotebooks}} +
+
+ +
+
+ +
+
+ {{end}} + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ The notebooks will shared to register user +
+ +
+ +
+ {{range $note := .arrMap.registerSharedNotes}} +
+
+ +
+
+ +
+
+ {{end}} + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ The notes will shared to register user +
+ +
+ +
+ {{range $noteId := .arr.registerCopyNoteIds}} +
+
+ +
+
+ {{end}} + +
+
+ +
+
+ +
+
+ +
+
+
+ The notes will copy to register user +
+
+ +
+ +
+
+
+
+ +
+ +{{template "admin/footer.html" .}} + + + +{{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/Admin/User/add.html b/app/views/Admin/User/add.html index ab4681b..c7cfac3 100644 --- a/app/views/Admin/User/add.html +++ b/app/views/Admin/User/add.html @@ -45,7 +45,7 @@ $(function() { ajaxPost("/auth/doRegister", getFormJsonData("add_user_form"), function(ret){ $(t).button('reset') if(!ret.Ok) { - art.alert(ret.Msg) + art.alert(ret.Msg); } else { art.tips("Success"); } diff --git a/app/views/Admin/User/list.html b/app/views/Admin/User/list.html index 4aa4bb6..32dcfe7 100644 --- a/app/views/Admin/User/list.html +++ b/app/views/Admin/User/list.html @@ -1,24 +1,18 @@ {{template "admin/top.html" .}} -

User

+

Users

-
@@ -90,7 +84,7 @@ {{range .users}} - + {{.Email}} @@ -103,15 +97,9 @@ {{.CreatedTime|datetime}} - - - - - - - Send Email + Send Email {{end}} @@ -122,32 +110,21 @@
-
- - showing 20-30 of 50 items - -
-
+ +
{{set . "url" (urlConcat "/adminUser/index" "sorter" .sorter "keywords" .keywords)}} {{template "admin/user/page.html" .}} -
@@ -157,6 +134,23 @@ diff --git a/app/views/Admin/footer.html b/app/views/Admin/footer.html index a895bd6..6522874 100644 --- a/app/views/Admin/footer.html +++ b/app/views/Admin/footer.html @@ -5,32 +5,26 @@
- - - - - - - - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/app/views/Admin/index.html b/app/views/Admin/index.html index bcec1f5..bac5a64 100644 --- a/app/views/Admin/index.html +++ b/app/views/Admin/index.html @@ -1,15 +1,15 @@ {{template "admin/top.html" .}} -

Workset

Welcome you!
+

Dashboard

@@ -30,51 +40,10 @@

Leanote Events

-
    -
  • -

    - Wellcome - - @Drew Wllon - - and play this web application template, have fun1 -

    - - - - 2 minuts ago - -
  • -
  • -

    - Morbi nec - - @Jonathan George - - nunc condimentum ipsum dolor sit amet, consectetur -

    - - - - 1 hour ago - -
  • -
  • -

    - - @Josh Long - - Vestibulum ullamcorper sodales nisi nec adipiscing elit. -

    - - - - 2 hours ago - -
  • -
+
    + + --> {{template "admin/footer.html" .}} + {{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/Admin/nav.html b/app/views/Admin/nav.html index 32b9f0c..6093526 100644 --- a/app/views/Admin/nav.html +++ b/app/views/Admin/nav.html @@ -1,8 +1,21 @@ \ No newline at end of file diff --git a/app/views/Admin/top.html b/app/views/Admin/top.html index 072036f..272f577 100644 --- a/app/views/Admin/top.html +++ b/app/views/Admin/top.html @@ -26,18 +26,28 @@
    {{.userBlog.AboutMe | raw}} +
    - - - {{template "blog/comment.html" .}}
    diff --git a/app/views/Blog/comment.html b/app/views/Blog/comment.html index 936a9d3..5ec5ebb 100644 --- a/app/views/Blog/comment.html +++ b/app/views/Blog/comment.html @@ -1,4 +1,190 @@ -{{if .userBlog.CanComment}} + +
    +
    + + {{if .blog.ReadNum}}{{.blog.ReadNum}}{{else}}1{{end}} {{msg . "viewers"}} +
    +
    +
    + + + + + + {{if eq .locale "zh"}} + + {{end}} +
    +
    +
    +
    + + +{{if and .userBlog.CanComment (not (eq .userBlog.CommentType "disqus"))}} + + + + +
    + {{if .visitUserInfo.UserId}} +
    +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    + {{else}} +
    + {{msg . "signIn"}}, {{msg . "submitComment"}}. +
    + 没有帐号? {{msg . "signUp"}} +
    + {{end}} +
    + + {{.blog.CommentNum}} {{msg . "comments"}} + +
    +
      +
    +
    + +
    +
    + More... +
    +
    + +
    +
    + +{{if eq .locale "zh"}} +
    +
    +
      +
    • +
    • +
    • +
    • +
    • +
    + +

    +
    +
    +{{end}} + +{{end}} + +{{if and .userBlog.CanComment (eq .userBlog.CommentType "disqus")}} +
    - - + + + + +{{if not .isMe}} + +{{end}} \ No newline at end of file diff --git a/app/views/Blog/index.html b/app/views/Blog/index.html index dcb4d54..5f863b6 100644 --- a/app/views/Blog/index.html +++ b/app/views/Blog/index.html @@ -3,7 +3,7 @@
    {{if .notebookId}} -

    {{msg . "blogClass"}}: {{.notebook.Title}}

    +

    {{msg . "blogClass"}} - {{.notebook.Title}}

    {{end}}
    @@ -11,30 +11,35 @@ {{range .blogs}}
    - + {{if .Tags}} - {{blogTags .Tags}} + {{blogTags $ .Tags}} {{else}} - {{msg $G "noTag"}} + {{msg $ "noTag"}} {{end}} | - {{msg $G "updatedTime"}} {{.UpdatedTime | datetime}} | - {{msg $G "createdTime"}} {{.CreatedTime | datetime}} + {{msg $ "updatedTime"}} {{.UpdatedTime | datetime}} | + {{msg $ "createdTime"}} {{.CreatedTime | datetime}}
    {{.Content | raw}}
    - More... + {{msg $ "more"}}.
    {{end}}
      - {{page .userInfo.Username .notebookId .page .pageSize .count}} + {{if .notebookId}} + {{set $ "pageUrl" (concatStr $.cateUrl "/" .notebookId)}} + {{else}} + {{set $ "pageUrl" $.indexUrl}} + {{end}} + {{page $.pageUrl .page .pageSize .count}}
    diff --git a/app/views/Blog/search.html b/app/views/Blog/search.html index 82590aa..cc012a7 100644 --- a/app/views/Blog/search.html +++ b/app/views/Blog/search.html @@ -2,38 +2,38 @@
    -

    搜索 {{.key}}

    +

    {{msg . "search"}} - {{.key}}

    {{range .blogs}}
    - + {{if .Tags}} - {{blogTags .Tags}} + {{blogTags $ .Tags}} {{else}} - 无 + {{msg $ "noTag"}} {{end}} | - 更新 {{.UpdatedTime | datetime}} | - 创建 {{.CreatedTime | datetime}} + {{msg $ "updatedTime"}} {{.UpdatedTime | datetime}} | + {{msg $ "createdTime"}} {{.CreatedTime | datetime}}
    {{.Content | raw}}
    - More... + {{msg $ "more"}}
    {{end}} {{if not .blogs }}
    - 无 + {{msg . "none"}}
    {{end}}
    diff --git a/app/views/Blog/set.html b/app/views/Blog/set.html index 6111643..d078f6c 100644 --- a/app/views/Blog/set.html +++ b/app/views/Blog/set.html @@ -1,11 +1,12 @@ {{template "Blog/header.html" .}} - - + +
    @@ -20,9 +21,18 @@
    - +
    -
    +
    +
    + +
    + +
    + -
    +
    -
    - +
    - + +
    +
    +
    + +
    + +
    - {{msg . "commentSys"}} -
    - -
    - {{msg . "disqusHelp"}} - {{msg . "needHelp"}}) + +
    + + + + +
    + +
    + {{msg . "disqusHelp"}} + {{msg . "needHelp"}} +
    -
    -
    - - -
    +
    +
    +
    + +
    +
    @@ -101,7 +143,7 @@
    -
    @@ -158,11 +200,11 @@ {{template "Blog/footer.html" .}} - - - + +
    diff --git a/app/views/Blog/view.html b/app/views/Blog/view.html index e850e6a..0c76ff5 100644 --- a/app/views/Blog/view.html +++ b/app/views/Blog/view.html @@ -1,6 +1,5 @@ {{template "Blog/header.html" .}} -
    @@ -8,15 +7,30 @@ {{.blog.Title}}
    - + {{if .blog.Tags}} - {{blogTags .blog.Tags}} + {{blogTags $ .blog.Tags}} {{else}} {{msg . "noTag"}} {{end}} | - {{msg . "updatedTime"}} {{.blog.UpdatedTime | datetime}} | - {{msg . "createdTime"}} {{.blog.CreatedTime | datetime}} + {{msg . "updatedTime"}} {{.blog.UpdatedTime | datetime}} | + {{msg . "createdTime"}} {{.blog.CreatedTime | datetime}} +
    + +
    + {{ if .userInfo.Logo}} + + {{else}} + + {{end}} + {{.userInfo.Username}} + + {{if .blog.Tags}} +   + + {{blogTags $ .blog.Tags}} + {{end}}
    @@ -33,75 +47,41 @@ {{else}} {{.blog.Content | raw}} {{end}} + +
    {{.blog.Desc}}
    {{template "blog/comment.html" .}}
    -
    {{template "Blog/footer.html" .}} {{template "Blog/highlight.html"}} - - -
    {{msg . "blogNav"}}
    -
    +
    - + + + + + + + + {{if .blog.IsMarkdown }} - @@ -127,13 +107,19 @@ prettyPrint(); MathJax.Hub.Queue(["Typeset",MathJax.Hub,"wmd-preview"]); initNav(); +weixin(); {{else}} {{end}} + + \ No newline at end of file diff --git a/app/views/Errors/500.html b/app/views/Errors/500.html index e64da01..683f4c4 100644 --- a/app/views/Errors/500.html +++ b/app/views/Errors/500.html @@ -6,7 +6,7 @@
    -

    404

    +

    500

    diff --git a/app/views/Home/header.html b/app/views/Home/header.html index b4cf595..9d90671 100644 --- a/app/views/Home/header.html +++ b/app/views/Home/header.html @@ -22,6 +22,51 @@ function log(o) { +

    +
  • {{msg . "download"}}
  • {{msg . "donate"}}
  • -
  • lea++
  • +
  • lea++
  • {{msg . "discussion"}}
  • -
    -
    \ No newline at end of file +
    +--> \ No newline at end of file diff --git a/app/views/Home/login.html b/app/views/Home/login.html index 29d5386..6b71b99 100644 --- a/app/views/Home/login.html +++ b/app/views/Home/login.html @@ -1,4 +1,13 @@ {{template "home/header_box.html" .}} + + +
    + +
    +
    +
    + {{else}} + {{.content | raw}} + {{end}} +
    +
    + + + + + + + + + + +{{if .blog.IsMarkdown }} + + + + + + + + + + + + +{{end}} + + \ No newline at end of file diff --git a/app/views/Html2Image/test.html b/app/views/Html2Image/test.html new file mode 100644 index 0000000..e134cd2 --- /dev/null +++ b/app/views/Html2Image/test.html @@ -0,0 +1,4 @@ +lif------------ +e + +you can \ No newline at end of file diff --git a/app/views/Note/note-dev.html b/app/views/Note/note-dev.html index 013a465..060c698 100644 --- a/app/views/Note/note-dev.html +++ b/app/views/Note/note-dev.html @@ -3,17 +3,19 @@ - + + + - -leanote, {{msg $ "moto"}} + +leanote, Not Just A Notebook - + - + - + - + + + - - - - + @@ -998,64 +862,28 @@ LEA.locale = "{{.locale}}"; Notebook.renderNotebooks(notebooks); Share.renderShareNotebooks(sharedUserInfos, shareNotebooks); +Note.setNoteCache(noteContentJson); Note.renderNotes(notes); if(!isEmpty(notes)) { Note.changeNote(notes[0].NoteId); } -Note.setNoteCache(noteContentJson); -Note.renderNoteContent(noteContentJson) +// Note.chanteNote设置content +// Note.renderNoteContent(noteContentJson) Tag.renderTagNav(tagsJson); // init notebook后才调用 initSlimScroll(); - - - - - - - - - - - - - - - - - - - + - + + - \ No newline at end of file diff --git a/app/views/Note/note.html b/app/views/Note/note.html index 33affa7..030c1f4 100755 --- a/app/views/Note/note.html +++ b/app/views/Note/note.html @@ -3,17 +3,19 @@ - + + + - -leanote, {{msg $ "moto"}} + +leanote, Not Just A Notebook - + - + - + - + + + - - - - + @@ -1002,64 +862,28 @@ LEA.locale = "{{.locale}}"; Notebook.renderNotebooks(notebooks); Share.renderShareNotebooks(sharedUserInfos, shareNotebooks); +Note.setNoteCache(noteContentJson); Note.renderNotes(notes); if(!isEmpty(notes)) { Note.changeNote(notes[0].NoteId); } -Note.setNoteCache(noteContentJson); -Note.renderNoteContent(noteContentJson) +// Note.chanteNote设置content +// Note.renderNoteContent(noteContentJson) Tag.renderTagNav(tagsJson); // init notebook后才调用 initSlimScroll(); - - - - - - - - - - - - - - - - - - - + - + + - \ No newline at end of file diff --git a/app/views/Oauth/oauth_callback_error.html b/app/views/Oauth/oauth_callback_error.html index 7aa0047..5a2b2c7 100644 --- a/app/views/Oauth/oauth_callback_error.html +++ b/app/views/Oauth/oauth_callback_error.html @@ -1,18 +1,32 @@ {{template "home/header_box.html" .}} -
    +
    -

    - leanote | we got a error -

    - -

    - Sorry, we can't get your infomation. -
    - Please Sign in Or Sign up -

    - +

    leanote

    +
    +
    We got a error
    +
    +
    + Sorry, we can't get your infomation. + +
    + Please {{msg . "login"}} Or {{msg . "register"}} +
    +
    +
    + +
    +

    + {{msg . "login"}} +   + {{msg . "home"}} +

    +

    + leanote © 2014 +

    +
    + \ No newline at end of file diff --git a/app/views/Share/note_notebook_share_user_infos.html b/app/views/Share/note_notebook_share_user_infos.html index 8e9bf3d..8940b59 100644 --- a/app/views/Share/note_notebook_share_user_infos.html +++ b/app/views/Share/note_notebook_share_user_infos.html @@ -1,37 +1,36 @@