diff --git a/app/controllers/AttachController.go b/app/controllers/AttachController.go index d7f2134..97d18f9 100644 --- a/app/controllers/AttachController.go +++ b/app/controllers/AttachController.go @@ -100,7 +100,7 @@ func (c Attach) uploadAttach(noteId string) (re info.Re) { id := bson.NewObjectId(); fileInfo.AttachId = id fileId = id.Hex() - Ok, resultMsg = attachService.AddAttach(fileInfo) + Ok, resultMsg = attachService.AddAttach(fileInfo, false) if resultMsg != "" { resultMsg = c.Message(resultMsg) } diff --git a/app/controllers/BlogController.go b/app/controllers/BlogController.go index 98c6c36..b5bf765 100644 --- a/app/controllers/BlogController.go +++ b/app/controllers/BlogController.go @@ -200,29 +200,77 @@ func (c Blog) getCates(userBlog info.UserBlog) { } var i = 0 - cates := make([]map[string]string, len(notebooks)) + cates := make([]*info.Cate, len(notebooks)) // 先要保证已有的是正确的排序 cateIds := userBlog.CateIds has := map[string]bool{} // cateIds中有的 + cateMap := map[string]*info.Cate{} if cateIds != nil && len(cateIds) > 0 { for _, cateId := range cateIds { if n, ok := notebooksMap[cateId]; ok { - cates[i] = map[string]string{"Title": n.Title, "UrlTitle": c.getCateUrlTitle(&n), "CateId": n.NotebookId.Hex()} + parentNotebookId := "" + if n.ParentNotebookId != "" { + parentNotebookId = n.ParentNotebookId.Hex() + } + cates[i] = &info.Cate{Title: n.Title, UrlTitle: c.getCateUrlTitle(&n), CateId: n.NotebookId.Hex(), ParentCateId: parentNotebookId} + cateMap[cates[i].CateId] = cates[i] i++ has[cateId] = true } } } - // 之后 + + // 之后添加没有排序的 for _, n := range notebooks { id := n.NotebookId.Hex() if !has[id] { - cates[i] = map[string]string{"Title": n.Title, "UrlTitle": c.getCateUrlTitle(&n), "CateId": id} + parentNotebookId := "" + if n.ParentNotebookId != "" { + parentNotebookId = n.ParentNotebookId.Hex() + } + cates[i] = &info.Cate{Title: n.Title, UrlTitle: c.getCateUrlTitle(&n), CateId: id, ParentCateId: parentNotebookId} + cateMap[cates[i].CateId] = cates[i] i++ } } + +// LogJ(">>") +// LogJ(cates) + + // 建立层级 + hasParent := map[string]bool{} // 有父的cate + for _, cate := range cates { + parentCateId := cate.ParentCateId + if parentCateId != "" { + if parentCate, ok := cateMap[parentCateId]; ok { +// Log("________") +// LogJ(parentCate) +// LogJ(cate) + if parentCate.Children == nil { + parentCate.Children = []*info.Cate{cate} + } else { + parentCate.Children = append(parentCate.Children, cate) + } + hasParent[cate.CateId] = true + } + } + } + + // 得到没有父的cate, 作为第一级cate + catesTree := []*info.Cate{} + for _, cate := range cates { + if !hasParent[cate.CateId] { + catesTree = append(catesTree, cate) + } + } + + Log("cates") + LogJ(cates) + LogJ(catesTree); + c.RenderArgs["cates"] = cates + c.RenderArgs["catesTree"] = catesTree } // 单页 diff --git a/app/controllers/NoteController.go b/app/controllers/NoteController.go index 59c43bb..94d1c09 100644 --- a/app/controllers/NoteController.go +++ b/app/controllers/NoteController.go @@ -7,6 +7,7 @@ import ( . "github.com/leanote/leanote/app/lea" "github.com/leanote/leanote/app/info" "os/exec" + "strings" // "time" // "github.com/leanote/leanote/app/types" // "io/ioutil" @@ -124,9 +125,7 @@ func (c Note) Index(noteId string) revel.Result { c.RenderArgs["globalConfigs"] = configService.GetGlobalConfigForUser() - -// return c.RenderTemplate("note/note.html") - +// return c.RenderTemplate("note/note.html") if isDev, _ := revel.Config.Bool("mode.dev"); isDev { return c.RenderTemplate("note/note-dev.html") } else { @@ -169,7 +168,7 @@ type NoteOrContent struct { Title string Desc string ImgSrc string - Tags []string + Tags string Content string Abstract string IsNew bool @@ -192,7 +191,7 @@ func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result { NoteId: bson.ObjectIdHex(noteOrContent.NoteId), NotebookId: bson.ObjectIdHex(noteOrContent.NotebookId), Title: noteOrContent.Title, - Tags: noteOrContent.Tags, + Tags: strings.Split(noteOrContent.Tags, ","), Desc: noteOrContent.Desc, ImgSrc: noteOrContent.ImgSrc, IsBlog: noteOrContent.IsBlog, @@ -225,23 +224,32 @@ func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result { noteUpdate["Title"] = noteOrContent.Title; } - if c.Has("Tags[]") { + if c.Has("Tags") { needUpdateNote = true - noteUpdate["Tags"] = noteOrContent.Tags; + noteUpdate["Tags"] = strings.Split(noteOrContent.Tags, ","); } + // web端不控制 if needUpdateNote { - noteService.UpdateNote(noteOrContent.UserId, c.GetUserId(), - noteOrContent.NoteId, noteUpdate) + noteService.UpdateNote(c.GetUserId(), + noteOrContent.NoteId, noteUpdate, -1) } //------------- - + afterContentUsn := 0 + contentOk := false + contentMsg := "" if c.Has("Content") { - noteService.UpdateNoteContent(noteOrContent.UserId, c.GetUserId(), - noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract) +// noteService.UpdateNoteContent(noteOrContent.UserId, c.GetUserId(), +// noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract) + contentOk, contentMsg, afterContentUsn = noteService.UpdateNoteContent(c.GetUserId(), + noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract, needUpdateNote, -1) } + Log(afterContentUsn) + Log(contentOk) + Log(contentMsg) + return c.RenderJson(true) } @@ -291,10 +299,12 @@ func (c Note) SearchNoteByTags(tags []string) revel.Result { // 这是脚本调用, 没有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("") @@ -413,6 +423,75 @@ func (c Note) Html2Image(noteId string) revel.Result { } +// 导出成PDF +func (c Note) ExportPdf(noteId string) revel.Result { + re := info.NewRe() + userId := c.GetUserId() + note := noteService.GetNoteById(noteId) + if note.NoteId == "" { + re.Msg = "No Note" + return c.RenderJson(re) + } + + 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/" + noteUserId + "/images/weibo" + dir := revel.BasePath + "/public/" + fileUrlPath + if !ClearDir(dir) { + re.Msg = "No Dir" + return c.RenderJson(re) + } + filename := note.Title + ".pdf"; + if note.Title == "" { + filename = "Untitled.pdf"; + } + path := dir + "/" + filename + + // 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:] + } + Log(cookieValue) + + appKey, _ := revel.Config.String("app.secret") +// cookieDomain, _ := revel.Config.String("cookie.domain") + // 生成之 + url := configService.GetSiteUrl() + "/note/toImage?noteId=" + noteId + "&appKey=" + appKey; + // /Users/life/Documents/bin/phantomjs/bin/phantomjs /Users/life/Desktop/test/b.js + binPath := "/usr/local/bin/wkhtmltopdf" // configService.GetGlobalStringConfig("toImageBinPath") + if binPath == "" { + return c.RenderJson(re); + } +// cc := binPath + " \"" + url + "\" \"" + path + "\" \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + 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) +} + // 设置/取消Blog; 置顶 func (c Note) SetNote2Blog(noteId string, isBlog, isTop bool) revel.Result { re := noteService.ToBlog(c.GetUserId(), noteId, isBlog, isTop) diff --git a/app/controllers/NotebookController.go b/app/controllers/NotebookController.go index db1d7d9..5d2ab4c 100644 --- a/app/controllers/NotebookController.go +++ b/app/controllers/NotebookController.go @@ -37,7 +37,8 @@ func (c Notebook) AddNotebook(notebookId, title, parentNotebookId string) revel. if(parentNotebookId != "") { notebook.ParentNotebookId = bson.ObjectIdHex(parentNotebookId) } - re := notebookService.AddNotebook(notebook) + + re, notebook := notebookService.AddNotebook(notebook) if(re) { return c.RenderJson(notebook) @@ -74,4 +75,4 @@ func (c Notebook) DragNotebooks(data string) revel.Result { func (c Notebook) SetNotebook2Blog(notebookId string, isBlog bool) revel.Result { re := notebookService.ToBlog(c.GetUserId(), notebookId, isBlog) return c.RenderJson(re) -} \ No newline at end of file +} diff --git a/app/controllers/TagController.go b/app/controllers/TagController.go new file mode 100644 index 0000000..96778dc --- /dev/null +++ b/app/controllers/TagController.go @@ -0,0 +1,30 @@ +package controllers + +import ( + "github.com/revel/revel" +// "encoding/json" +// "gopkg.in/mgo.v2/bson" +// . "github.com/leanote/leanote/app/lea" + "github.com/leanote/leanote/app/info" +// "os/exec" +) + +type Tag struct { + BaseController +} + +// 更新Tag +func (c Tag) UpdateTag(tag string) revel.Result { + ret := info.NewRe() + ret.Ok = true + ret.Item = tagService.AddOrUpdateTag(c.GetUserId(), tag) + return c.RenderJson(ret) +} + +// 删除标签 +func (c Tag) DeleteTag(tag string) revel.Result { + ret := info.Re{} + ret.Ok = true + ret.Item = tagService.DeleteTag(c.GetUserId(), tag) + return c.RenderJson(ret) +} \ No newline at end of file diff --git a/app/controllers/admin/AdminUpgradeController.go b/app/controllers/admin/AdminUpgradeController.go index 3835d68..8f8f2f3 100644 --- a/app/controllers/admin/AdminUpgradeController.go +++ b/app/controllers/admin/AdminUpgradeController.go @@ -21,4 +21,10 @@ func (c AdminUpgrade) UpgradeBetaToBeta2() revel.Result { re := info.NewRe() re.Ok, re.Msg = upgradeService.UpgradeBetaToBeta2(c.GetUserId()) return c.RenderJson(re) +} + +func (c AdminUpgrade) UpgradeBeta3ToBeta4() revel.Result { + re := info.NewRe() + re.Ok, re.Msg = upgradeService.Api(c.GetUserId()) + return c.RenderJson(re) } \ No newline at end of file diff --git a/app/controllers/api/API列表-v0.1.md b/app/controllers/api/API列表-v0.1.md new file mode 100644 index 0000000..d4957d5 --- /dev/null +++ b/app/controllers/api/API列表-v0.1.md @@ -0,0 +1,463 @@ +# API 列表 + +## 前言 + +### api url + +所有api的url前面带/api/, 如: + +`/api/user/info?userId=xxxx&token=xxxx` + +除了/auth/login, /auth/register外其它的都需要另外带参数token=xxxx + +### 文件目录结构 +* 所有API的Controller都在app/api文件夹下 +* 文件命名: Api功能Controller.go, 如ApiUserController.go +* 结构体命名为 Api功能, 如ApiUser +* API公用Controller: ApiBaseController +* init.go 注入service和定义拦截器 + +### 流程 +用户登录后返回一个token, 以后所有的请求都携带该token. +在init.go中的拦截器会得到token并调用sessionService判断是否登录了 + +## 返回值结构 +* 全部返回JSON, JSON, 除二进制文件(图片, 附件外), 如果返回其它非JSON格式的值, 肯定是出错了 +* 错误信息全部返回 {Ok: false, Msg: "相应的错误信息"} +* 正确信息返回分两种: +1. 一些操作型的api, 比如updateUsername, updateLogo之类的, 成功后返回 {Ok: true, Msg:""} +2. 一些获取型api, 如getNote, 全部返回真实的返回数据, 如返回笔记: +``` +{ + "NoteId": "54bdc7e305fcd13ea3000000", + "NotebookId": "54bdc65599c37b0da9000003", + "UserId": "54bdc65599c37b0da9000002", + "Title": "笔记标题", + "Desc": "", + "Tags": null, + "Abstract": "", + "Content": "", + "IsMarkdown": false, + "IsBlog": false, + "IsTrash": true, + "IsDeleted": false, + "Usn": 15, + "Files": [], + "CreatedTime": "2015-01-20T11:13:41.34+08:00", + "UpdatedTime": "2015-01-20T11:13:41.34+08:00", + "PublicTime": "0001-01-01T00:00:00Z" +} +``` +* 时间类型全是返回如 "2015-01-20T11:13:41.34+08:00" 的格式, (因为golang的time转成Json就是这样, 历史原因) + +----------------- + +## Auth 登录与注册 + +### /auth/login 登录 + +``` +参数: email, pwd +Method: GET +返回: +错误: {"Ok":false, "Msg":"用户名或密码有误"} +正确: 比如: +{ + "Ok":true, + "Token":"5500830738f41138e90003232", + "UserId":"52d26b4e99c37b609a000001", + "Email":"leanote@leanote.com", + "Username":"leanote" +} +``` + +登录成功后将使用token作为之后的请求 + +### /auth/logout 注销 +``` +参数: token +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: {Ok: true, Msg: ""} +``` + +### /auth/register 注册 +``` +参数: email, pwd +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: {Ok: true, Msg: ""} +``` + +## User 用户 +### /user/info 获取用户信息 +``` +参数: userId +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: type.User +``` + +### /user/updateUsername 修改用户名 +``` +参数: username(新用户名) +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: {Ok: true, Msg: ""} +``` + +### /user/updatePwd 修改密码 +``` +参数: oldPwd(旧密码), pwd(新密码) +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: {Ok: true, Msg: ""} +``` + +### /user/updateLogo 修改头像 +``` +参数: file(文件) +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: {Ok: true, Msg: ""} +``` + +### /user/getSyncState 获取最新同步状态 +``` +参数: 无 +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: {LastSyncUsn: 3232, LastSyncTime: "上次同步时间"(暂时无用)} +``` + +----- + +## Notebook 笔记本 + +### /notebook/getSyncNotebooks 得到需要同步的笔记本 +``` +参数: afterUsn(int, 在此usn后的笔记本是需要同步的), maxEntry(int, 最大要同步的量) +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: [type.Notebook] 数组 +``` + +### /notebook/getNotebooks 得到所有笔记本 +``` +无参数 +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: [type.Notebook] 数组 +``` + +### /notebook/addNotebook 添加笔记本 +``` +参数: title(string), parentNotebookId(string, 父notebookId, 可空), seq(int) 排列 +Method: POST +返回: +错误: {Ok: false, Msg:""} +成功: type.Notebook +``` + +### /notebook/updateNotebook 修改笔记本 +``` +参数: notebookId, title, parentNotebookId, seq(int), usn(int) +Method: POST +返回: +错误: {Ok: false, msg: ""} msg == "conflict" 表示冲突 +成功: type.Notebook +``` + +### /notebook/deleteNotebook 删除笔记本 +``` +参数: notebookId, usn(int) +Method: GET +返回: +错误: {Ok: false, msg: ""} msg == "conflict" 表示冲突 +成功: {Ok: true} +``` + +---- + +## Note 笔记 + + +### /note/getSyncNotes 获取需要同步的笔记 +``` +参数: afterUsn(int, 在此usn后的笔记是需要同步的), maxEntry(int, 最大要同步的量) +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: [type.Note] 数组, 笔记不包含Abstract和Content +``` + + +### /note/getNotes 获得某笔记本下的笔记(无内容) +``` +参数: notebookId +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: [type.Note] 数组, 笔记不包含Abstract和Content +``` + +### /note/getNoteAndContent 获得笔记与内容 +``` +参数: noteId +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: type.Note +``` + +### /note/getNoteContent 获得笔记内容 +``` +参数: noteId +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: type.NoteContent +``` + +### /note/addNote 添加笔记 +``` +参数: (注意首字大写) + NotebookId string 必传 + Title string 必传 + Tags []string 可选 + Content string 必传 + Abstract string 可选, 当是markdown笔记时必须传 + IsMarkdown bool 可选 + Files []type.NoteFiles 数组 可选 +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: type.Note, 不包含Abstract, Content +``` + +** 关于笔记中的图片/附件** +客户端应该添加一个"图片/附件表"来存元数据, 图片应该要缓存到本地, 附件可在需要的时候再调用相应api获取. + +Content中的数据, 图片,附件在Leanote的链接是, 所以, 不管你在本地的笔记中是以什么形式来保存图片,附件的链接的,请addNote和updateNote时务必将链接修改成leanote服务器上的链接. +http://leanote.com/api/file/getImage?fileId=xx +单个附件: +http://leanote.com/api/file/getAttach?fileId=xx +所有附件: +http://leanote.com/api/file/getAllAttachs?noteId=xxx +``` + +**注意:** +addNote时必须要把Files, 和相关的图片/附件一起传到服务器中 +其中Files(文件的元数据)和其它字段以POST方式传出, 而真正数据则以http的multipart传入, 每个文件的name为"FileDatas[LocalFileId]" + +图片在笔记内的链接必须为: http://leanote.com/api/file/getImage?fileId=LocalFileId或FileId +附件如果插入到了笔记内容内, 其链接必须为: http://leanote.com/api/file/getAttach?fileId=LocalFileId或FileId +其中, fileId为文件在本地的LocalFileId或服务器上的FileId + +服务器端会生成FileId传给Client. Client在本地必须要建立LocalFileId与FileId的关联. + +如果笔记内容传了, 且笔记内有图片, 则必须要传Files 文件元数据, 因为Server端还要对内容内的图片, 附件链接进行修改, 可能你传过去的是LocalFileId, 服务器端会将LocalFileId替换成FileId存到数据库中. + +同样适用于 updateNote + +http://leanote.com 不绝对, 因为用户可以自建服务, 所以在开发时需要可配置 + +### /note/updateNote 更新笔记 +当更新了笔记某个属性时, 只要传某个属性就行, 其它不用传, 比如把笔记拉入了trash, 那么就传IsTrash: true +``` +参数: (注意首字大写) + NoteId string 必传 + Usn int 必传 + NotebookId string 可选 + Title string 可选 + Tags []string 可选 + Content string 可选 + Abstract string 可选, 当是markdown笔记时必须传 + IsMarkdown bool 可选 + IsTrash bool 是否是trash 可选 + Files []type.NoteFiles 数组 可选 +Method: POST +返回: +错误: {Ok: false, msg: ''} msg == 'conflict' 表示冲突 +成功: type.Note, 不包含Abstract和Content +``` + +### /note/deleteTrash 彻底删除笔记 +``` +参数: noteId, usn +Method: GET +返回: +错误: {Ok: false, msg: ''} msg == 'conflict' 表示冲突 +成功: type.UpdateRet +``` + +------- + +## Tag 标签 + +### /tag/getSyncTags 获取需要同步的标签 +``` +参数: afterUsn(int, 在此usn后的标签是需要同步的), maxEntry(int, 最大要同步的量) +Method: GET +返回: +错误: {Ok: false, Msg: ""} +成功: [type.Tag] 数组 +``` + +### /tag/addTag 添加标签 +``` +参数: tag(string) +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: type.Tag +``` + +### /tag/deleteTag 删除标签 +``` +参数: tag(string) +Method: POST +返回: +错误: {Ok: false, Msg: ""} +成功: type.UpdateRet +``` + +### File 文件(获取图片, 附件) + +### /file/getImage 获取图片 +``` +参数: fileId +Method: GET +返回: +错误: 非二进制文件数据 +成功: 二进制文件 +``` + +### /file/getAttach 获取附件 +``` +参数: fileId +Method: GET +返回: +错误: 非二进制文件数据 +成功: 二进制文件 +``` + +### /file/getAllAttachs 获取所有附件 +``` +参数: noteId +Method: GET +返回: +错误: 非二进制文件数据 +成功: 二进制文件 +``` + +-------- + +## 数据类型 + +### type.User 用户信息 + +``` +User { + UserId string + Username string + Email string + Verified bool + Logo string +} +``` + +### type.Notebook 笔记本 + +``` +Notebook { + NotebookId + UserId + ParentNotebookId // 上级 + Seq int // 排序 + Title string + IsBlog bool + CreatedTime time.Time + UpdatedTime time.Time + + // 更新序号 + Usn int // UpdateSequenceNum +} +``` + +### type.Note 笔记 +``` +Note { + NoteId string + NotebookId string + UserId string + Title string + Tags []string + Content string + IsMarkdown bool + IsBlog bool + IsTrash bool + Files []NoteFile // 图片, 附件 + CreatedTime time.Time + UpdatedTime time.Time + PublicTime time.Time + + // 更新序号 + Usn int +} +``` + +### type.NoteContent 笔记内容 +``` +NoteContent { + NoteId string + UserId string + Content string +} +``` + +### type.NoteFile 笔记文件(图片,附件) +``` +NoteFile { + FileId string // 服务器端Id + LocalFileId string // 客户端Id + Type string // images/png, doc, xls, 根据fileName确定 + Title string + HasBody bool // 传过来的值是否要更新内容, 如果有true, 则必须传文件 + IsAttach bool // 是否是附件, 不是附件就是图片 +} +``` + +### type.Tag 标签 +``` +Tag { + TagId string + UserId string + Tag string + CreatedTime + UpdatedTime + IsDeleted bool // 删除位 + // 更新序号 + Usn +} +``` + +### type.UpdateRe 更新后返回的值, 包含Usn +``` +ReUpdate { + Ok bool + Msg string + + // 更新序号 + Usn int +} +``` diff --git a/app/controllers/api/API设计-v0.1.md b/app/controllers/api/API设计-v0.1.md new file mode 100644 index 0000000..56e84dd --- /dev/null +++ b/app/controllers/api/API设计-v0.1.md @@ -0,0 +1,47 @@ +# API设计 + +By life (life@leanote.com) + +## api url + +所有api的url前面带/api/, 如: + +`/api/user/info?userId=xxxx&token=xxxx` + +## 文件目录结构 +* 所有API的Controller都在app/api文件夹下 +* 文件命名: Api功能Controller.go, 如ApiUserController.go +* 结构体命名为 Api功能, 如ApiUser +* API公用Controller: ApiBaseController +* init.go 注入service和定义拦截器 + +## 流程 +用户登录后返回一个token, 以后所有的请求都携带该token. +在init.go中的拦截器会得到token并调用sessionService判断是否登录了 + +## 返回值结构 +* 全部返回JSON, JSON, 除二进制文件(图片, 附件外), 如果返回其它非JSON格式的值, 肯定是出错了 +* 错误信息全部返回 {Ok: false, Msg: ""} +* 正确信息全部返回真实的返回数据, 如返回笔记: +``` +{ + "NoteId": "54bdc7e305fcd13ea3000000", + "NotebookId": "54bdc65599c37b0da9000003", + "UserId": "54bdc65599c37b0da9000002", + "Title": "asdfads", + "Desc": "", + "Tags": null, + "Abstract": "", + "Content": "", + "IsMarkdown": false, + "IsBlog": false, + "IsTrash": true, + "IsDeleted": false, + "Usn": 15, + "Files": [], + "CreatedTime": "2015-01-20T11:13:41.34+08:00", + "UpdatedTime": "2015-01-20T11:13:41.34+08:00", + "PublicTime": "0001-01-01T00:00:00Z" +} +``` +* 时间类型全是返回如 "2015-01-20T11:13:41.34+08:00" 的格式, (因为golang的time转成Json就是这样, 历史原因) \ No newline at end of file diff --git a/app/controllers/api/ApiAuthController.go b/app/controllers/api/ApiAuthController.go new file mode 100644 index 0000000..e4f35aa --- /dev/null +++ b/app/controllers/api/ApiAuthController.go @@ -0,0 +1,69 @@ +package api + +import ( + "github.com/leanote/leanote/app/info" + . "github.com/leanote/leanote/app/lea" + "github.com/revel/revel" + "gopkg.in/mgo.v2/bson" + // "strconv" +) + +// 用户登录后生成一个token, 将这个token保存到session中 +// 以后每次的请求必须带这个token, 并从session中获取userId + +// 用户登录/注销/找回密码 + +type ApiAuth struct { + ApiBaseContrller +} + +// 登录 +// [ok] +// 成功返回 {Ok: true, Item: token } +// 失败返回 {Ok: false, Msg: ""} +func (c ApiAuth) Login(email, pwd string) revel.Result { + var msg = "" + + userInfo := authService.Login(email, pwd) + if userInfo.Email != "" { + token := bson.NewObjectId().Hex() + sessionService.SetUserId(token, userInfo.UserId.Hex()) + return c.RenderJson(info.AuthOk{Ok: true, Token: token, UserId: userInfo.UserId, Email: userInfo.Email, Username: userInfo.Username}) + } else { + // 登录错误, 则错误次数++ + msg = "wrongUsernameOrPassword" + } + return c.RenderJson(info.ApiRe{Ok: false, Msg: c.Message(msg)}) +} + +// 注销 +// [Ok] +func (c ApiAuth) Logout() revel.Result { + token := c.getToken() + sessionService.Clear(token) + re := info.NewApiRe() + re.Ok = true + return c.RenderJson(re) +} + +// 注册 +// [Ok] +// 成功后并不返回用户ID, 需要用户重新登录 +func (c ApiAuth) Register(email, pwd string) revel.Result { + re := info.NewApiRe() + if !configService.IsOpenRegister() { + re.Msg = "notOpenRegister" // 未开放注册 + return c.RenderJson(re) + } + + if re.Ok, re.Msg = Vd("email", email); !re.Ok { + return c.RenderJson(re) + } + if re.Ok, re.Msg = Vd("password", pwd); !re.Ok { + return c.RenderJson(re) + } + + // 注册 + re.Ok, re.Msg = authService.Register(email, pwd, "") + return c.RenderJson(re) +} \ No newline at end of file diff --git a/app/controllers/api/ApiBaseController.go b/app/controllers/api/ApiBaseController.go new file mode 100644 index 0000000..4c70c06 --- /dev/null +++ b/app/controllers/api/ApiBaseController.go @@ -0,0 +1,181 @@ +package api + +import ( + "github.com/revel/revel" + "gopkg.in/mgo.v2/bson" + // "encoding/json" + . "github.com/leanote/leanote/app/lea" + "github.com/leanote/leanote/app/controllers" + "github.com/leanote/leanote/app/info" + "os" +// "fmt" + "io/ioutil" + // "fmt" + // "math" + // "strconv" + "strings" +) + +// 公用Controller, 其它Controller继承它 +type ApiBaseContrller struct { + controllers.BaseController // 不能用*BaseController +} + +// 得到token, 这个token是在AuthInterceptor设到Session中的 +func (c ApiBaseContrller) getToken() string { + return c.Session["_token"] +} + +// userId +// _userId是在AuthInterceptor设置的 +func (c ApiBaseContrller) getUserId() string { + return c.Session["_userId"] +} + +// 得到用户信息 +func (c ApiBaseContrller) getUserInfo() info.User { + userId := c.Session["_userId"] + if userId == "" { + return info.User{} + } + return userService.GetUserInfo(userId) +} + +// 上传附件 +func (c ApiBaseContrller) uploadAttach(name string, noteId string) (ok bool, msg string, id string) { + userId := c.getUserId(); + + // 判断是否有权限为笔记添加附件 + // 如果笔记还没有添加是不是会有问题 + /* + if !shareService.HasUpdateNotePerm(noteId, userId) { + return + } + */ + + file, handel, err := c.Request.FormFile(name) + if err != nil { + return + } + defer file.Close() + + data, err := ioutil.ReadAll(file) + if err != nil { + return + } + // > 5M? + maxFileSize := configService.GetUploadSize("uploadAttachSize"); + if maxFileSize <= 0 { + maxFileSize = 1000 + } + if(float64(len(data)) > maxFileSize * float64(1024*1024)) { + msg = "fileIsTooLarge" + return + } + + // 生成上传路径 + filePath := "files/" + userId + "/attachs" + dir := revel.BasePath + "/" + filePath + err = os.MkdirAll(dir, 0755) + if err != nil { + return + } + // 生成新的文件名 + filename := handel.Filename + _, ext := SplitFilename(filename) // .doc + filename = NewGuid() + ext + toPath := dir + "/" + filename; + err = ioutil.WriteFile(toPath, data, 0777) + if err != nil { + return + } + + // add File to db + fileType := "" + if ext != "" { + fileType = strings.ToLower(ext[1:]) + } + filesize := GetFilesize(toPath) + fileInfo := info.Attach{AttachId: bson.NewObjectId(), + Name: filename, + Title: handel.Filename, + NoteId: bson.ObjectIdHex(noteId), + UploadUserId: bson.ObjectIdHex(userId), + Path: filePath + "/" + filename, + Type: fileType, + Size: filesize} + + ok, msg = attachService.AddAttach(fileInfo, true) + if !ok { + return + } + + id = fileInfo.AttachId.Hex() + return +} + +// 上传图片 +func (c ApiBaseContrller) upload(name string, noteId string, isAttach bool) (ok bool, msg string, id string) { + if isAttach { + return c.uploadAttach(name, noteId) + } + file, handel, err := c.Request.FormFile(name) + if err != nil { + return + } + defer file.Close() + // 生成上传路径 + fileUrlPath := "files/" + c.getUserId() + "/images" + dir := revel.BasePath + "/" + fileUrlPath + err = os.MkdirAll(dir, 0755) + if err != nil { + return + } + // 生成新的文件名 + filename := handel.Filename + _, ext := SplitFilename(filename) + if(ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg") { + msg = "notImage" + return + } + + filename = NewGuid() + ext + data, err := ioutil.ReadAll(file) + if err != nil { + return + } + + maxFileSize := configService.GetUploadSize("uploadImageSize"); + if maxFileSize <= 0 { + maxFileSize = 1000 + } + + // > 2M? + if(float64(len(data)) > maxFileSize * float64(1024*1024)) { + msg = "fileIsTooLarge" + return + } + + toPath := dir + "/" + filename; + err = ioutil.WriteFile(toPath, data, 0777) + if err != nil { + return + } + // 改变成gif图片 + _, toPathGif := TransToGif(toPath, 0, true) + filename = GetFilename(toPathGif) + filesize := GetFilesize(toPathGif) + fileUrlPath += "/" + filename + + // File + fileInfo := info.File{FileId: bson.NewObjectId(), + Name: filename, + Title: handel.Filename, + Path: fileUrlPath, + Size: filesize} + ok, msg = fileService.AddImage(fileInfo, "", c.getUserId(), true) + if ok { + id = fileInfo.FileId.Hex() + } + return +} diff --git a/app/controllers/api/ApiFileController.go b/app/controllers/api/ApiFileController.go new file mode 100644 index 0000000..530e9fe --- /dev/null +++ b/app/controllers/api/ApiFileController.go @@ -0,0 +1,164 @@ +package api + +import ( + "github.com/revel/revel" + // "encoding/json" +// . "github.com/leanote/leanote/app/lea" +// "gopkg.in/mgo.v2/bson" + // "github.com/leanote/leanote/app/lea/netutil" +// "github.com/leanote/leanote/app/info" +// "io/ioutil" + "os" + // "strconv" + "io" + "time" + "strings" + "archive/tar" + "compress/gzip" +) + +// 文件操作, 图片, 头像上传, 输出 + +type ApiFile struct { + ApiBaseContrller +} + +/* +// 协作时复制图片到owner +func (c ApiFile) CopyImage(userId, fileId, toUserId string) revel.Result { + re := info.NewRe() + + re.Ok, re.Id = fileService.CopyImage(userId, fileId, toUserId) + + return c.RenderJson(re) +} + +// get all images by userId with page +func (c ApiFile) GetImages(albumId, key string, page int) revel.Result { + imagesPage := fileService.ListImagesWithPage(c.getUserId(), albumId, key, page, 12) + re := info.NewRe() + re.Ok = true + re.Item = imagesPage + return c.RenderJson(re) +} + +func (c ApiFile) UpdateImageTitle(fileId, title string) revel.Result { + re := info.NewRe() + re.Ok = fileService.UpdateImageTitle(c.getUserId(), fileId, title) + return c.RenderJson(re) +} + +func (c ApiFile) DeleteImage(fileId string) revel.Result { + re := info.NewRe() + re.Ok, re.Msg = fileService.DeleteImage(c.getUserId(), fileId) + return c.RenderJson(re) +} + +*/ + +//----------- + +// 输出image +// [OK] +func (c ApiFile) GetImage(fileId string) revel.Result { + path := fileService.GetFile(c.getUserId(), fileId) // 得到路径 + if path == "" { + return c.RenderText("") + } + fn := revel.BasePath + "/" + strings.TrimLeft(path, "/") + file, _ := os.Open(fn) + return c.RenderFile(file, revel.Inline) // revel.Attachment +} + +// 下载附件 +// [OK] +func (c ApiFile) GetAttach(fileId string) revel.Result { + attach := attachService.GetAttach(fileId, c.getUserId()); // 得到路径 + path := attach.Path + if path == "" { + return c.RenderText("No Such File") + } + fn := revel.BasePath + "/" + strings.TrimLeft(path, "/") + file, _ := os.Open(fn) + return c.RenderBinary(file, attach.Title, revel.Attachment, time.Now()) // revel.Attachment +} + +// 下载所有附件 +// [OK] +func (c ApiFile) GetAllAttachs(noteId string) revel.Result { + note := noteService.GetNoteById(noteId) + if note.NoteId == "" { + return c.RenderText("") + } + // 得到文件列表 + attachs := attachService.ListAttachs(noteId, c.getUserId()) + if attachs == nil || len(attachs) == 0 { + return c.RenderText("") + } + + /* + dir := revel.BasePath + "/files/tmp" + err := os.MkdirAll(dir, 0755) + if err != nil { + return c.RenderText("") + } + */ + + filename := note.Title + ".tar.gz" + if note.Title == "" { + filename = "all.tar.gz" + } + + // file write + fw, err := os.Create(revel.BasePath + "/files/" + filename) + if err != nil { + return c.RenderText("") + } + // defer fw.Close() // 不需要关闭, 还要读取给用户下载 + + // gzip write + gw := gzip.NewWriter(fw) + defer gw.Close() + + // tar write + tw := tar.NewWriter(gw) + defer tw.Close() + + // 遍历文件列表 + for _, attach := range attachs { + fn := revel.BasePath + "/" + strings.TrimLeft(attach.Path, "/") + fr, err := os.Open(fn) + fileInfo, _ := fr.Stat() + if err != nil { + return c.RenderText("") + } + defer fr.Close() + + // 信息头 + h := new(tar.Header) + h.Name = attach.Title + h.Size = fileInfo.Size() + h.Mode = int64(fileInfo.Mode()) + h.ModTime = fileInfo.ModTime() + + // 写信息头 + err = tw.WriteHeader(h) + if err != nil { + panic(err) + } + + // 写文件 + _, err = io.Copy(tw, fr) + if err != nil { + panic(err) + } + } // for + +// tw.Close() +// gw.Close() +// fw.Close() +// file, _ := os.Open(dir + "/" + filename) + // fw.Seek(0, 0) + return c.RenderBinary(fw, filename, revel.Attachment, time.Now()) // revel.Attachment +} + diff --git a/app/controllers/api/ApiNoteController.go b/app/controllers/api/ApiNoteController.go new file mode 100644 index 0000000..700d250 --- /dev/null +++ b/app/controllers/api/ApiNoteController.go @@ -0,0 +1,573 @@ +package api + +import ( + "github.com/revel/revel" + // "encoding/json" + "github.com/leanote/leanote/app/info" + . "github.com/leanote/leanote/app/lea" + "gopkg.in/mgo.v2/bson" + "regexp" + "strings" + "time" + // "github.com/leanote/leanote/app/types" + // "io/ioutil" + // "fmt" + // "bytes" + // "os" +) + +// 笔记API + +type ApiNote struct { + ApiBaseContrller +} + +// 获取同步的笔记 +// > afterUsn的笔记 +// 无Desc, Abstract, 有Files +/* + { + "NoteId": "55195fa199c37b79be000005", + "NotebookId": "55195fa199c37b79be000002", + "UserId": "55195fa199c37b79be000001", + "Title": "Leanote语法Leanote语法Leanote语法Leanote语法Leanote语法", + "Desc": "", + "Tags": null, + "Abstract": "", + "Content": "", + "IsMarkdown": true, + "IsBlog": false, + "IsTrash": false, + "Usn": 5, + "Files": [], + "CreatedTime": "2015-03-30T22:37:21.695+08:00", + "UpdatedTime": "2015-03-30T22:37:21.724+08:00", + "PublicTime": "2015-03-30T22:37:21.695+08:00" + } +*/ +func (c ApiNote) GetSyncNotes(afterUsn, maxEntry int) revel.Result { + if maxEntry == 0 { + maxEntry = 100 + } + notes := noteService.GetSyncNotes(c.getUserId(), afterUsn, maxEntry) + return c.RenderJson(notes) +} + +// 得到笔记本下的笔记 +// [OK] +func (c ApiNote) GetNotes(notebookId string) revel.Result { + if notebookId != "" && !bson.IsObjectIdHex(notebookId) { + re := info.NewApiRe() + re.Msg = "notebookIdInvalid" + return c.RenderJson(re) + } + _, notes := noteService.ListNotes(c.getUserId(), notebookId, false, c.GetPage(), pageSize, defaultSortField, false, false) + return c.RenderJson(noteService.ToApiNotes(notes)) +} + +// 得到trash +// [OK] +func (c ApiNote) GetTrashNotes() revel.Result { + _, notes := noteService.ListNotes(c.getUserId(), "", true, c.GetPage(), pageSize, defaultSortField, false, false) + return c.RenderJson(noteService.ToApiNotes(notes)) +} + +// get Note +// [OK] +/* +{ + "NoteId": "550c0bee2ec82a2eb5000000", + "NotebookId": "54a1676399c37b1c77000004", + "UserId": "54a1676399c37b1c77000002", + "Title": "asdfadsf--=", + "Desc": "", + "Tags": [ + "" + ], + "Abstract": "", + "Content": "", + "IsMarkdown": false, + "IsBlog": false, + "IsTrash": false, + "Usn": 8, + "Files": [ + { + "FileId": "551975d599c37b970f000002", + "LocalFileId": "", + "Type": "", + "Title": "", + "HasBody": false, + "IsAttach": false + }, + { + "FileId": "551975de99c37b970f000003", + "LocalFileId": "", + "Type": "doc", + "Title": "李铁-简历-ali-print-en.doc", + "HasBody": false, + "IsAttach": true + }, + { + "FileId": "551975de99c37b970f000004", + "LocalFileId": "", + "Type": "doc", + "Title": "李铁-简历-ali-print.doc", + "HasBody": false, + "IsAttach": true + } + ], + "CreatedTime": "2015-03-20T20:00:52.463+08:00", + "UpdatedTime": "2015-03-31T00:12:44.967+08:00", + "PublicTime": "2015-03-20T20:00:52.463+08:00" +} +*/ +func (c ApiNote) GetNote(noteId string) revel.Result { + if !bson.IsObjectIdHex(noteId) { + re := info.NewApiRe() + re.Msg = "noteIdInvalid" + return c.RenderJson(re) + } + + note := noteService.GetNote(noteId, c.getUserId()) + if note.NoteId == "" { + re := info.NewApiRe() + re.Msg = "notExists" + return c.RenderJson(re) + } + apiNotes := noteService.ToApiNotes([]info.Note{note}) + return c.RenderJson(apiNotes[0]) +} + +// 得到note和内容 +// [OK] +func (c ApiNote) GetNoteAndContent(noteId string) revel.Result { + noteAndContent := noteService.GetNoteAndContent(noteId, c.getUserId()) + + apiNotes := noteService.ToApiNotes([]info.Note{noteAndContent.Note}) + apiNote := apiNotes[0] + apiNote.Content = noteAndContent.Content + return c.RenderJson(apiNote) +} + +// 处理笔记内容数据 http://leanote.com/file/outputImage -> https://leanote.com/api/file/getImage +// 图片, 附件都替换 +func (c ApiNote) fixContent(content string) string { + // TODO, 这个url需要从config中取 +// baseUrl := "http://leanote.com" + baseUrl := configService.GetSiteUrl() +// baseUrl := "http://localhost:9000" + + patterns := []map[string]string{ + map[string]string{"src": "src", "middle": "/file/outputImage", "param": "fileId", "to": "getImage?fileId="}, + map[string]string{"src": "href", "middle": "/attach/download", "param": "attachId", "to": "getAttach?fileId="}, + map[string]string{"src": "href", "middle": "/attach/downloadAll", "param": "noteId", "to": "getAllAttachs?noteId="}, + } + + for _, eachPattern := range patterns { + + // src="http://leanote.com/file/outputImage?fileId=5503537b38f4111dcb0000d1" + // href="http://leanote.com/attach/download?attachId=5504243a38f4111dcb00017d" + // href="http://leanote.com/attach/downloadAll?noteId=55041b6a38f4111dcb000159" + + regImage, _ := regexp.Compile(eachPattern["src"] + `=('|")`+ baseUrl + eachPattern["middle"] + `\?` + eachPattern["param"] + `=([a-z0-9A-Z]{24})("|')`) + findsImage := regImage.FindAllStringSubmatch(content, -1) // 查找所有的 + + // [[src='http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069" ' 54672e8d38f411286b000069 "] [src="http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069" " 54672e8d38f411286b000069 "] [src="http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069" " 54672e8d38f411286b000069 "] [src="http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069" " 54672e8d38f411286b000069 "]] + for _, eachFind := range findsImage { + // [src='http://leanote.com/file/outputImage?fileId=54672e8d38f411286b000069" ' 54672e8d38f411286b000069 "] + if len(eachFind) == 4 { + content = strings.Replace(content, + eachFind[0], + eachPattern["src"] + "=\"" + baseUrl + "/api/file/" + eachPattern["to"] + eachFind[2] + "\"", + 1); + } + } + + // markdown处理 + // ![](http://leanote.com/file/outputImage?fileId=5503537b38f4111dcb0000d1) + // [selection 2.html](http://leanote.com/attach/download?attachId=5504262638f4111dcb00017f) + // [all.tar.gz](http://leanote.com/attach/downloadAll?noteId=5503b57d59f81b4eb4000000) + + pre := "!" // 默认图片 + if eachPattern["src"] == "href" { // 是attach + pre = "" + } + + regImageMarkdown, _ := regexp.Compile(pre + `\[(.*?)\]\(`+ baseUrl + eachPattern["middle"] + `\?` + eachPattern["param"] + `=([a-z0-9A-Z]{24})\)`) + findsImageMarkdown := regImageMarkdown.FindAllStringSubmatch(content, -1) // 查找所有的 + // [[![](http://leanote.com/file/outputImage?fileId=5503537b38f4111dcb0000d1) 5503537b38f4111dcb0000d1] [![你好啊, 我很好, 为什么?](http://leanote.com/file/outputImage?fileId=5503537b38f4111dcb0000d1) 5503537b38f4111dcb0000d1]] + for _, eachFind := range findsImageMarkdown { + // [![你好啊, 我很好, 为什么?](http://leanote.com/file/outputImage?fileId=5503537b38f4111dcb0000d1) 你好啊, 我很好, 为什么? 5503537b38f4111dcb0000d1] + if len(eachFind) == 3 { + content = strings.Replace(content, eachFind[0], pre + "[" + eachFind[1] + "](" + baseUrl + "/api/file/" + eachPattern["to"] + eachFind[2] + ")", 1); + } + } + } + + return content +} + +// content里的image, attach链接是 +// https://leanote.com/api/file/getImage?fileId=xx +// https://leanote.com/api/file/getAttach?fileId=xx +// 将fileId=映射成ServerFileId, 这里的fileId可能是本地的FileId +func (c ApiNote) fixPostNotecontent(noteOrContent *info.ApiNote) { + if noteOrContent.Content == "" { + return + } + files := noteOrContent.Files + if files != nil && len(files) > 0 { + for _, file := range files { + if file.LocalFileId != "" { + noteOrContent.Content = strings.Replace(noteOrContent.Content, "fileId=" + file.LocalFileId, "fileId=" + file.FileId, -1) + } + } + } +} + +// 得到内容 +// [OK] +func (c ApiNote) GetNoteContent(noteId string) revel.Result { + // re := info.NewRe() + noteContent := noteService.GetNoteContent(noteId, c.getUserId()) + if noteContent.Content != "" { + noteContent.Content = c.fixContent(noteContent.Content) + } + + apiNoteContent := info.ApiNoteContent{ + NoteId: noteContent.NoteId, + UserId: noteContent.UserId, + Content: noteContent.Content, + } + + // re.Item = noteContent + return c.RenderJson(apiNoteContent) +} + +// 添加笔记 +// [OK] +func (c ApiNote) AddNote(noteOrContent info.ApiNote) revel.Result { + userId := bson.ObjectIdHex(c.getUserId()) + re := info.NewRe() + myUserId := userId + // 为共享新建? + /* + if noteOrContent.FromUserId != "" { + userId = bson.ObjectIdHex(noteOrContent.FromUserId) + } + */ + // Log(noteOrContent.Title) + // LogJ(noteOrContent) + /* + LogJ(c.Params) + for name, _ := range c.Params.Files { + Log(name) + file, _, _ := c.Request.FormFile(name) + LogJ(file) + } + */ + // return c.RenderJson(re) + if noteOrContent.NotebookId == "" || !bson.IsObjectIdHex(noteOrContent.NotebookId) { + re.Msg = "notebookIdNotExists" + return c.RenderJson(re) + } + + noteId := bson.NewObjectId() + // TODO 先上传图片/附件, 如果不成功, 则返回false + // + attachNum := 0 + if noteOrContent.Files != nil && len(noteOrContent.Files) > 0 { + for i, file := range noteOrContent.Files { + if file.HasBody { + if file.LocalFileId != "" { + // FileDatas[54c7ae27d98d0329dd000000] + ok, msg, fileId := c.upload("FileDatas["+file.LocalFileId+"]", noteId.Hex(), file.IsAttach) + + if !ok { + re.Ok = false + if msg != "" { + Log(msg) + Log(file.LocalFileId) + re.Msg = "fileUploadError" + } + // 报不是图片的错误没关系, 证明客户端传来非图片的数据 + if msg != "notImage" { + return c.RenderJson(re) + } + } else { + // 建立映射 + file.FileId = fileId + noteOrContent.Files[i] = file + + if file.IsAttach { + attachNum++ + } + } + } else { + return c.RenderJson(re) + } + } + } + } + + c.fixPostNotecontent(¬eOrContent) + +// Log("Add") +// LogJ(noteOrContent) + + // return c.RenderJson(re) + + note := info.Note{UserId: userId, + NoteId: noteId, + NotebookId: bson.ObjectIdHex(noteOrContent.NotebookId), + Title: noteOrContent.Title, + Tags: noteOrContent.Tags, + Desc: noteOrContent.Desc, +// ImgSrc: noteOrContent.ImgSrc, + IsBlog: noteOrContent.IsBlog, + IsMarkdown: noteOrContent.IsMarkdown, + AttachNum: attachNum, + } + noteContent := info.NoteContent{NoteId: note.NoteId, + UserId: userId, + IsBlog: note.IsBlog, + Content: noteOrContent.Content, + Abstract: noteOrContent.Abstract} + + // 通过内容得到Desc, abstract + note.Desc = SubStringHTMLToRaw(noteContent.Content, 50) + noteContent.Abstract = SubStringHTML(noteContent.Content, 200, "") + + note = noteService.AddNoteAndContentApi(note, noteContent, myUserId) + + if note.NoteId == "" { + re.Ok = false + return c.RenderJson(re) + } + + // 添加需要返回的 + noteOrContent.NoteId = note.NoteId.Hex() + noteOrContent.Usn = note.Usn + noteOrContent.CreatedTime = note.CreatedTime + noteOrContent.UpdatedTime = note.UpdatedTime + noteOrContent.UserId = c.getUserId() + noteOrContent.IsMarkdown = note.IsMarkdown + // 删除一些不要返回的 + noteOrContent.Content = "" + noteOrContent.Abstract = "" + // apiNote := info.NoteToApiNote(note, noteOrContent.Files) + return c.RenderJson(noteOrContent) +} + +// 更新笔记 +// [OK] +func (c ApiNote) UpdateNote(noteOrContent info.ApiNote) revel.Result { + re := info.NewReUpdate() + + noteUpdate := bson.M{} + needUpdateNote := false + + noteId := noteOrContent.NoteId + + if noteOrContent.NoteId == "" { + re.Msg = "noteIdNotExists" + return c.RenderJson(re) + } + + if noteOrContent.Usn <= 0 { + re.Msg = "usnNotExists" + return c.RenderJson(re) + } + +// Log("_____________") +// LogJ(noteOrContent) + /* + LogJ(c.Params.Files) + LogJ(c.Request.Header) + LogJ(c.Params.Values) + */ + + // 先判断USN的问题, 因为很可能添加完附件后, 会有USN冲突, 这时附件就添错了 + userId := c.getUserId() + note := noteService.GetNote(noteId, userId) + if note.NoteId == "" { + re.Msg = "notExists" + return c.RenderJson(re) + } + if note.Usn != noteOrContent.Usn { + re.Msg = "conflict" + Log("conflict") + return c.RenderJson(re) + } + + // 如果传了files + // TODO 测试 + /* + for key, v := range c.Params.Values { + Log(key) + Log(v) + } + */ +// Log(c.Has("Files[0]")) + if c.Has("Files[0][LocalFileId]") { +// LogJ(c.Params.Files) + if noteOrContent.Files != nil && len(noteOrContent.Files) > 0 { + for i, file := range noteOrContent.Files { + if file.HasBody { + if file.LocalFileId != "" { + // FileDatas[54c7ae27d98d0329dd000000] + ok, msg, fileId := c.upload("FileDatas["+file.LocalFileId+"]", noteId, file.IsAttach) + if !ok { + Log("upload file error") + re.Ok = false + if msg == "" { + re.Msg = "fileUploadError" + } else { + re.Msg = msg + } + return c.RenderJson(re) + } else { + // 建立映射 + file.FileId = fileId + noteOrContent.Files[i] = file + } + } else { + return c.RenderJson(re) + } + } + } + } + +// Log("after upload") +// LogJ(noteOrContent.Files) + } + + // 移到外面来, 删除最后一个file时也要处理, 不然总删不掉 + // 附件问题, 根据Files, 有些要删除的, 只留下这些 + attachService.UpdateOrDeleteAttachApi(noteId, userId, noteOrContent.Files) + + // Desc前台传来 + if c.Has("Desc") { + needUpdateNote = true + noteUpdate["Desc"] = noteOrContent.Desc + } + /* + if c.Has("ImgSrc") { + needUpdateNote = true + noteUpdate["ImgSrc"] = noteOrContent.ImgSrc + } + */ + if c.Has("Title") { + needUpdateNote = true + noteUpdate["Title"] = noteOrContent.Title + } + if c.Has("IsTrash") { + needUpdateNote = true + noteUpdate["IsTrash"] = noteOrContent.IsTrash + } + + // 是否是博客 + if c.Has("IsBlog") { + needUpdateNote = true + noteUpdate["IsBlog"] = noteOrContent.IsBlog + } + + /* + Log(c.Has("tags[0]")) + Log(c.Has("Tags[]")) + for key, v := range c.Params.Values { + Log(key) + Log(v) + } + */ + + if c.Has("Tags[0]") { + needUpdateNote = true + noteUpdate["Tags"] = noteOrContent.Tags + } + + if c.Has("NotebookId") { + if bson.IsObjectIdHex(noteOrContent.NotebookId) { + needUpdateNote = true + noteUpdate["NotebookId"] = bson.ObjectIdHex(noteOrContent.NotebookId) + } + } + + if c.Has("Content") { + // 通过内容得到Desc, abstract + noteUpdate["Desc"] = SubStringHTMLToRaw(noteOrContent.Content, 50) + } + + afterNoteUsn := 0 + noteOk := false + noteMsg := "" + if needUpdateNote { + noteOk, noteMsg, afterNoteUsn = noteService.UpdateNote(c.getUserId(), noteOrContent.NoteId, noteUpdate, noteOrContent.Usn) + if !noteOk { + re.Ok = false + re.Msg = noteMsg + return c.RenderJson(re) + } + } + + //------------- + afterContentUsn := 0 + contentOk := false + contentMsg := "" + if c.Has("Content") { + // 把fileId替换下 + c.fixPostNotecontent(¬eOrContent) + // 如果传了Abstract就用之 + if noteOrContent.Abstract == "" { + noteOrContent.Abstract = SubStringHTML(noteOrContent.Content, 200, "") + } +// Log("--------> afte fixed") +// Log(noteOrContent.Content) + contentOk, contentMsg, afterContentUsn = noteService.UpdateNoteContent(c.getUserId(), + noteOrContent.NoteId, noteOrContent.Content, noteOrContent.Abstract, needUpdateNote, noteOrContent.Usn) + } + + if needUpdateNote { + re.Ok = noteOk + re.Msg = noteMsg + re.Usn = afterNoteUsn + } else { + re.Ok = contentOk + re.Msg = contentMsg + re.Usn = afterContentUsn + } + + if !re.Ok { + return c.RenderJson(re) + } + + noteOrContent.Content = "" + noteOrContent.Usn = re.Usn + noteOrContent.UpdatedTime = time.Now() + +// Log("after upload") +// LogJ(noteOrContent.Files) + noteOrContent.UserId = c.getUserId() + + return c.RenderJson(noteOrContent) +} + +// 删除trash +func (c ApiNote) DeleteTrash(noteId string, usn int) revel.Result { + re := info.NewReUpdate() + re.Ok, re.Msg, re.Usn = trashService.DeleteTrashApi(noteId, c.getUserId(), usn) + return c.RenderJson(re) +} + +// 得到历史列表 +/* +func (c ApiNote) GetHistories(noteId string) revel.Result { + re := info.NewRe() + histories := noteContentHistoryService.ListHistories(noteId, c.getUserId()) + if len(histories) > 0 { + re.Ok = true + re.Item = histories + } + return c.RenderJson(re) +} +*/ \ No newline at end of file diff --git a/app/controllers/api/ApiNotebookController.go b/app/controllers/api/ApiNotebookController.go new file mode 100644 index 0000000..53e8c67 --- /dev/null +++ b/app/controllers/api/ApiNotebookController.go @@ -0,0 +1,106 @@ +package api + +import ( + "github.com/leanote/leanote/app/info" + "github.com/revel/revel" + "gopkg.in/mgo.v2/bson" + . "github.com/leanote/leanote/app/lea" + // "io/ioutil" +) + +// 笔记本API + +type ApiNotebook struct { + ApiBaseContrller +} + +// 从Notebook -> ApiNotebook +func (c ApiNotebook) fixNotebooks(notebooks []info.Notebook) []info.ApiNotebook { + if notebooks == nil { + return nil + } + apiNotebooks := make([]info.ApiNotebook, len(notebooks)) + for i, notebook := range notebooks { + apiNotebooks[i] = c.fixNotebook(¬ebook) + } + return apiNotebooks +} +func (c ApiNotebook) fixNotebook(notebook *info.Notebook) info.ApiNotebook { + if notebook == nil { + return info.ApiNotebook{} + } + return info.ApiNotebook{ + NotebookId : notebook.NotebookId, + UserId : notebook.UserId, + ParentNotebookId : notebook.ParentNotebookId, + Seq : notebook.Seq, + Title : notebook.Title, + UrlTitle : notebook.UrlTitle, + IsBlog : notebook.IsBlog, + CreatedTime : notebook.CreatedTime, + UpdatedTime : notebook.UpdatedTime, + Usn: notebook.Usn, + IsDeleted: notebook.IsDeleted, + } +} + +// 获取同步的笔记本 +// [OK] +// > afterUsn的笔记 +// 返回 {ChunkHighUsn: 本下最大的usn, 借此可以知道是否还有, Notebooks: []} +func (c ApiNotebook) GetSyncNotebooks(afterUsn, maxEntry int) revel.Result { + if maxEntry == 0 { + maxEntry = 100 + } + notebooks := notebookService.GeSyncNotebooks(c.getUserId(), afterUsn, maxEntry) + return c.RenderJson(c.fixNotebooks(notebooks)) +} + +// 得到用户的所有笔记本 +// [OK] +// info.SubNotebooks +func (c ApiNotebook) GetNotebooks() revel.Result { + notebooks := notebookService.GeSyncNotebooks(c.getUserId(), 0, 99999) + return c.RenderJson(c.fixNotebooks(notebooks)) +} + +// 添加notebook +// [OK] +func (c ApiNotebook) AddNotebook(title, parentNotebookId string, seq int) revel.Result { + notebook := info.Notebook{NotebookId: bson.NewObjectId(), + Title: title, + Seq: seq, + UserId: bson.ObjectIdHex(c.getUserId())} + if parentNotebookId != "" && bson.IsObjectIdHex(parentNotebookId) { + notebook.ParentNotebookId = bson.ObjectIdHex(parentNotebookId) + } + re := info.NewRe() + re.Ok, notebook = notebookService.AddNotebook(notebook) + if !re.Ok { + return c.RenderJson(re) + } + return c.RenderJson(c.fixNotebook(¬ebook)) +} + +// 修改笔记 +// [OK] +func (c ApiNotebook) UpdateNotebook(notebookId, title, parentNotebookId string, seq, usn int) revel.Result { + re := info.NewApiRe() + + ok, msg, notebook := notebookService.UpdateNotebookApi(c.getUserId(), notebookId, title, parentNotebookId, seq, usn) + if !ok { + re.Ok = false + re.Msg = msg + return c.RenderJson(re) + } + LogJ(notebook) + return c.RenderJson(c.fixNotebook(¬ebook)) +} + +// 删除笔记本 +// [OK] +func (c ApiNotebook) DeleteNotebook(notebookId string, usn int) revel.Result { + re := info.NewApiRe() + re.Ok, re.Msg = notebookService.DeleteNotebookForce(c.getUserId(), notebookId, usn) + return c.RenderJson(re) +} diff --git a/app/controllers/api/ApiTagController.go b/app/controllers/api/ApiTagController.go new file mode 100644 index 0000000..5b2c58f --- /dev/null +++ b/app/controllers/api/ApiTagController.go @@ -0,0 +1,56 @@ +package api + +import ( + "github.com/leanote/leanote/app/info" + "github.com/revel/revel" +// "gopkg.in/mgo.v2/bson" + // . "github.com/leanote/leanote/app/lea" + // "io/ioutil" +) + +// 标签API + +type ApiTag struct { + ApiBaseContrller +} + +// 获取同步的标签 +// [OK] +// > afterUsn的笔记 +// 返回 {ChunkHighUsn: 本下最大的usn, 借此可以知道是否还有, Notebooks: []} +func (c ApiTag) GetSyncTags(afterUsn, maxEntry int) revel.Result { + if maxEntry == 0 { + maxEntry = 100 + } + tags := tagService.GeSyncTags(c.getUserId(), afterUsn, maxEntry) + return c.RenderJson(tags) +} + +// 添加Tag +// [OK] +// 不会产生冲突, 即使里面有 +// 返回 +/* +{ + "TagId": "551978dd99c37b9bc5000001", + "UserId": "54a1676399c37b1c77000002", + "Tag": "32", + "Usn": 25, + "Count": 1, + "CreatedTime": "2015-03-31T00:25:01.149312407+08:00", + "UpdatedTime": "2015-03-31T00:25:01.149312407+08:00", + "IsDeleted": false +} +*/ +func (c ApiTag) AddTag(tag string) revel.Result { + ret := tagService.AddOrUpdateTag(c.getUserId(), tag) + return c.RenderJson(ret) +} + +// 删除标签 +// [OK] +func (c ApiTag) DeleteTag(tag string, usn int) revel.Result { + re := info.NewReUpdate() + re.Ok, re.Msg, re.Usn = tagService.DeleteTagApi(c.getUserId(), tag, usn) + return c.RenderJson(re) +} \ No newline at end of file diff --git a/app/controllers/api/ApiUserController.go b/app/controllers/api/ApiUserController.go new file mode 100644 index 0000000..1601b4f --- /dev/null +++ b/app/controllers/api/ApiUserController.go @@ -0,0 +1,155 @@ +package api + +import ( + "github.com/revel/revel" + // "encoding/json" + "gopkg.in/mgo.v2/bson" + "github.com/leanote/leanote/app/info" + . "github.com/leanote/leanote/app/lea" + "time" + // "github.com/leanote/leanote/app/types" + "io/ioutil" + // "fmt" + // "math" + "os" + + // "path" + // "strconv" +) + +type ApiUser struct { + ApiBaseContrller +} + +// 获取用户信息 +// [OK] +func (c ApiUser) Info() revel.Result { + re := info.NewApiRe() + + userInfo := c.getUserInfo() + if userInfo.UserId == "" { + return c.RenderJson(re) + } + apiUser := info.ApiUser{ + UserId: userInfo.UserId.Hex(), + Username: userInfo.Username, + Email: userInfo.Email, + Logo: userInfo.Logo, + Verified: userInfo.Verified, + } + return c.RenderJson(apiUser) +} + +// 修改用户名 +// [OK] +func (c ApiUser) UpdateUsername(username string) revel.Result { + re := info.NewApiRe() + if c.GetUsername() == "demo" { + re.Msg = "cannotUpdateDemo" + return c.RenderJson(re) + } + + if re.Ok, re.Msg = Vd("username", username); !re.Ok { + return c.RenderJson(re) + } + + re.Ok, re.Msg = userService.UpdateUsername(c.getUserId(), username) + return c.RenderJson(re) +} + +// 修改密码 +// [OK] +func (c ApiUser) UpdatePwd(oldPwd, pwd string) revel.Result { + re := info.NewApiRe() + if c.GetUsername() == "demo" { + re.Msg = "cannotUpdateDemo" + return c.RenderJson(re) + } + if re.Ok, re.Msg = Vd("password", oldPwd); !re.Ok { + return c.RenderJson(re) + } + if re.Ok, re.Msg = Vd("password", pwd); !re.Ok { + return c.RenderJson(re) + } + re.Ok, re.Msg = userService.UpdatePwd(c.getUserId(), oldPwd, pwd) + return c.RenderJson(re) +} + +// 获得同步状态 +// [OK] +func (c ApiUser) GetSyncState() revel.Result { + ret := bson.M{"LastSyncUsn": userService.GetUsn(c.getUserId()), "LastSyncTime": time.Now().Unix()} + return c.RenderJson(ret) +} + + +// 头像设置 +// 参数file=文件 +// 成功返回{Logo: url} 头像新url +// [OK] +func (c ApiUser) UpdateLogo() revel.Result { + ok, msg, url := c.uploadImage() + + if ok { + ok = userService.UpdateAvatar(c.getUserId(), url) + return c.RenderJson(map[string]string{"Logo": url}) + } else { + re := info.NewApiRe() + re.Msg = msg + return c.RenderJson(re) + } +} + +// 上传图片 +func (c ApiUser) uploadImage() (ok bool, msg, url string) { + var fileUrlPath = "" + ok = false + + file, handel, err := c.Request.FormFile("file") + if err != nil { + return + } + defer file.Close() + // 生成上传路径 + fileUrlPath = "public/upload/" + c.getUserId() + "/images/logo" + + dir := revel.BasePath + "/" + fileUrlPath + err = os.MkdirAll(dir, 0755) + if err != nil { + return + } + // 生成新的文件名 + filename := handel.Filename + + var ext string + + _, ext = SplitFilename(filename) + if ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg" { + msg = "notImage" + return + } + + filename = NewGuid() + ext + data, err := ioutil.ReadAll(file) + if err != nil { + LogJ(err) + return + } + + // > 5M? + if len(data) > 5*1024*1024 { + msg = "fileIsTooLarge" + return + } + + toPath := dir + "/" + filename + err = ioutil.WriteFile(toPath, data, 0777) + if err != nil { + LogJ(err) + return + } + + ok = true + url = configService.GetSiteUrl() + "/" + fileUrlPath + "/" + filename + return +} diff --git a/app/controllers/api/UserController.go b/app/controllers/api/UserController.go deleted file mode 100644 index 0e384b8..0000000 --- a/app/controllers/api/UserController.go +++ /dev/null @@ -1,27 +0,0 @@ -package api - -import ( - "github.com/revel/revel" -// "encoding/json" -// "gopkg.in/mgo.v2/bson" - . "github.com/leanote/leanote/app/lea" -// "github.com/leanote/leanote/app/info" -// "github.com/leanote/leanote/app/types" -// "io/ioutil" -// "fmt" -// "math" -// "os" -// "path" -// "strconv" -) - -type ApiUser struct { - *revel.Controller -} - -// 修改用户名, 需要重置session -func (c ApiUser) Info() revel.Result { - Log("APIUser"); - return c.RenderTemplate("home/index.html"); -// return nil; -} \ No newline at end of file diff --git a/app/controllers/api/init.go b/app/controllers/api/init.go new file mode 100644 index 0000000..28a9a69 --- /dev/null +++ b/app/controllers/api/init.go @@ -0,0 +1,144 @@ +package api + +import ( + "github.com/leanote/leanote/app/info" + "github.com/leanote/leanote/app/service" +// . "github.com/leanote/leanote/app/lea" + "github.com/revel/revel" + "strings" +) + +var userService *service.UserService +var noteService *service.NoteService +var trashService *service.TrashService +var notebookService *service.NotebookService +var noteContentHistoryService *service.NoteContentHistoryService +var authService *service.AuthService +var shareService *service.ShareService +var blogService *service.BlogService +var tagService *service.TagService +var pwdService *service.PwdService +var tokenService *service.TokenService +var suggestionService *service.SuggestionService +var albumService *service.AlbumService +var noteImageService *service.NoteImageService +var fileService *service.FileService +var attachService *service.AttachService +var configService *service.ConfigService +var emailService *service.EmailService +var sessionService *service.SessionService + +var pageSize = 1000 +var defaultSortField = "UpdatedTime" +var leanoteUserId = "admin" // 不能更改 + +// 状态 +const ( + S_DEFAULT = iota // 0 + S_NOT_LOGIN // 1 + S_WRONG_USERNAME_PASSWORD // 2 + S_WRONG_CAPTCHA // 3 + S_NEED_CAPTCHA // 4 + S_NOT_OPEN_REGISTER // 4 +) + +// 拦截器 +// 不需要拦截的url +var commonUrl = map[string]map[string]bool{"ApiAuth": map[string]bool{"Login": true, + "Register": true, + }, + // 文件的操作也不用登录, userId会从session中获取 + "ApiFile": map[string]bool{"GetImage": true, + "GetAttach": true, + "GetAllAttachs": true, + }, +} + +func needValidate(controller, method string) bool { + // 在里面 + if v, ok := commonUrl[controller]; ok { + // 在commonUrl里 + if _, ok2 := v[method]; ok2 { + return false + } + return true + } else { + // controller不在这里的, 肯定要验证 + return true + } +} + +// 这里得到token, 若不是login, logout等公用操作, 必须验证是否已登录 +func AuthInterceptor(c *revel.Controller) revel.Result { + // 得到token /api/user/info?userId=xxx&token=xxxxx + token := c.Params.Values.Get("token") + noToken := false + if token == "" { + // 若无, 则取sessionId + token = c.Session.Id() + noToken = true + } + c.Session["_token"] = token + + // 全部变成首字大写 + var controller = strings.Title(c.Name) + var method = strings.Title(c.MethodName) + + // 验证是否已登录 + // 通过sessionService判断该token下是否有userId, 并返回userId + userId := sessionService.GetUserId(token) + if noToken && userId == "" { + // 从session中获取, api/file/getImage, api/file/getAttach, api/file/getAllAttach + // 客户端 + userId, _ = c.Session["UserId"]; + } + c.Session["_userId"] = userId + + // 是否需要验证? + if !needValidate(controller, method) { + return nil + } + + if userId != "" { + return nil // 已登录 + } + + // 没有登录, 返回错误的信息, 需要登录 + re := info.NewApiRe() + re.Msg = "NOTLOGIN" + return c.RenderJson(re) +} + +func init() { + // interceptors + revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiAuth{}) + revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiUser{}) + revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiFile{}) + revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiNote{}) + revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiTag{}) + revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &ApiNotebook{}) +} + +// 最外层init.go调用 +// 获取service, 单例 +func InitService() { + notebookService = service.NotebookS + noteService = service.NoteS + noteContentHistoryService = service.NoteContentHistoryS + trashService = service.TrashS + shareService = service.ShareS + userService = service.UserS + tagService = service.TagS + blogService = service.BlogS + tokenService = service.TokenS + noteImageService = service.NoteImageS + fileService = service.FileS + albumService = service.AlbumS + attachService = service.AttachS + pwdService = service.PwdS + suggestionService = service.SuggestionS + authService = service.AuthS + configService = service.ConfigS + emailService = service.EmailS + sessionService = service.SessionS +} diff --git a/app/db/Mgo.go b/app/db/Mgo.go index 684ae10..c062860 100644 --- a/app/db/Mgo.go +++ b/app/db/Mgo.go @@ -28,7 +28,7 @@ var Groups *mgo.Collection var GroupUsers *mgo.Collection var Tags *mgo.Collection -//var TagNotes *mgo.Collection +var NoteTags *mgo.Collection var TagCounts *mgo.Collection var UserBlogs *mgo.Collection @@ -116,7 +116,7 @@ func Init(url, dbname string) { // tag Tags = Session.DB(dbname).C("tags") -// TagNotes = Session.DB(dbname).C("tag_notes") + NoteTags = Session.DB(dbname).C("note_tags") TagCounts = Session.DB(dbname).C("tag_count") // blog diff --git a/app/info/Api.go b/app/info/Api.go new file mode 100644 index 0000000..5b19413 --- /dev/null +++ b/app/info/Api.go @@ -0,0 +1,119 @@ +package info + +import ( + "time" + "gopkg.in/mgo.v2/bson" +) + +//--------- +// 数据结构 +//--------- +type NoteFile struct { + FileId string // 服务器端Id + LocalFileId string // 客户端Id + Type string // images/png, doc, xls, 根据fileName确定 + Title string + HasBody bool // 传过来的值是否要更新内容 + IsAttach bool // 是否是附件, 不是附件就是图片 +} +type ApiNote struct { + NoteId string + NotebookId string + UserId string + Title string + Desc string +// ImgSrc string + Tags []string + Abstract string + Content string + IsMarkdown bool +// FromUserId string // 为共享而新建 + IsBlog bool // 是否是blog, 更新note不需要修改, 添加note时才有可能用到, 此时需要判断notebook是否设为Blog + IsTrash bool + IsDeleted bool + Usn int + Files []NoteFile + CreatedTime time.Time + UpdatedTime time.Time + PublicTime time.Time +} + + +// 内容 +type ApiNoteContent struct { + NoteId bson.ObjectId `bson:"_id,omitempty"` + UserId bson.ObjectId `bson:"UserId"` + + Content string `Content` + +// CreatedTime time.Time `CreatedTime` +// UpdatedTime time.Time `UpdatedTime` +} + +// 转换 +func NoteToApiNote(note Note, files []NoteFile) ApiNote { + apiNote := ApiNote{} + return apiNote +} + +//---------- +// 用户信息 +//---------- + +type ApiUser struct { + UserId string + Username string + Email string + Verified bool + Logo string +} + +//---------- +// Notebook +//---------- +type ApiNotebook struct { + NotebookId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键 + UserId bson.ObjectId `bson:"UserId"` + ParentNotebookId bson.ObjectId `bson:"ParentNotebookId,omitempty"` // 上级 + Seq int `Seq` // 排序 + Title string `Title` // 标题 + UrlTitle string `UrlTitle` // Url标题 2014/11.11加 + IsBlog bool `IsBlog,omitempty` // 是否是Blog 2013/12/29 新加 + CreatedTime time.Time `CreatedTime,omitempty` + UpdatedTime time.Time `UpdatedTime,omitempty` + Usn int `Usn` // UpdateSequenceNum + IsDeleted bool `IsDeleted` +} + +//--------- +// api 返回 +//--------- + +// 一般返回 +type ApiRe struct { + Ok bool + Msg string +} + +func NewApiRe() ApiRe { + return ApiRe{Ok: false} +} + +// auth +type AuthOk struct { + Ok bool + Token string + UserId bson.ObjectId + Email string + Username string +} + +// 供notebook, note, tag更新的返回数据用 +type ReUpdate struct { + Ok bool + Msg string + Usn int +} +func NewReUpdate() ReUpdate { + return ReUpdate{Ok: false} +} \ No newline at end of file diff --git a/app/info/BlogCustom.go b/app/info/BlogCustom.go index c309c21..aed4b24 100644 --- a/app/info/BlogCustom.go +++ b/app/info/BlogCustom.go @@ -47,3 +47,11 @@ type Archive struct { MonthAchives []ArchiveMonth Posts []*Post } + +type Cate struct { + CateId string + ParentCateId string + Title string + UrlTitle string + Children []*Cate +} diff --git a/app/info/NoteInfo.go b/app/info/NoteInfo.go index a1bce49..1dd31f5 100644 --- a/app/info/NoteInfo.go +++ b/app/info/NoteInfo.go @@ -34,12 +34,17 @@ type Note struct { IsMarkdown bool `IsMarkdown` // 是否是markdown笔记, 默认是false 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"` // 如果共享了, 并可写, 那么可能是其它他修改了 + + // 2015/1/15, 更新序号 + Usn int `Usn` // UpdateSequenceNum + + IsDeleted bool `IsDeleted` // 删除位 } // 内容 diff --git a/app/info/NotebookInfo.go b/app/info/NotebookInfo.go index 8d0db19..baeb2a0 100644 --- a/app/info/NotebookInfo.go +++ b/app/info/NotebookInfo.go @@ -19,6 +19,10 @@ type Notebook struct { IsBlog bool `IsBlog,omitempty` // 是否是Blog 2013/12/29 新加 CreatedTime time.Time `CreatedTime,omitempty` UpdatedTime time.Time `UpdatedTime,omitempty` + + // 2015/1/15, 更新序号 + Usn int `Usn` // UpdateSequenceNum + IsDeleted bool `IsDeleted` } // 仅仅是为了返回前台 diff --git a/app/info/SessionInfo.go b/app/info/SessionInfo.go index a724626..d08386d 100644 --- a/app/info/SessionInfo.go +++ b/app/info/SessionInfo.go @@ -13,6 +13,8 @@ type Session struct { LoginTimes int `LoginTimes` // 登录错误时间 Captcha string `Captcha` // 验证码 + + UserId string `UserId` // API时有值UserId CreatedTime time.Time `CreatedTime` UpdatedTime time.Time `UpdatedTime` // 更新时间, expire这个时间会自动清空 diff --git a/app/info/TagInfo.go b/app/info/TagInfo.go index 722bd5f..c7e67de 100644 --- a/app/info/TagInfo.go +++ b/app/info/TagInfo.go @@ -2,6 +2,7 @@ package info import ( "gopkg.in/mgo.v2/bson" + "time" ) // 这里主要是为了统计每个tag的note数目 @@ -21,6 +22,18 @@ type Tag struct { Tags []string `Tags` } +// v2 版标签 +type NoteTag struct { + TagId bson.ObjectId `bson:"_id"` + UserId bson.ObjectId `UserId` // 谁的 + Tag string `Tag` // UserId, Tag是唯一索引 + Usn int `Usn` // Update Sequence Number + Count int `Count` // 笔记数 + CreatedTime time.Time `CreatedTime` + UpdatedTime time.Time `UpdatedTime` + IsDeleted bool `IsDeleted` // 删除位 +} + type TagCount struct { TagCountId bson.ObjectId `bson:"_id,omitempty"` UserId bson.ObjectId `UserId` // 谁的 @@ -28,6 +41,7 @@ type TagCount struct { IsBlog bool `IsBlog` // 是否是博客的tag统计 Count int `Count` // 统计数量 } + /* type TagsCounts []TagCount func (this TagsCounts) Len() int { @@ -39,4 +53,4 @@ func (this TagsCounts) Less(i, j int) bool { func (this TagsCounts) Swap(i, j int) { this[i], this[j] = this[j], this[i] } -*/ \ No newline at end of file +*/ diff --git a/app/info/UserInfo.go b/app/info/UserInfo.go index abd06e3..794544e 100644 --- a/app/info/UserInfo.go +++ b/app/info/UserInfo.go @@ -36,22 +36,26 @@ type User struct { ThirdType int `ThirdType` // 第三方类型 // 用户的帐户类型 - - ImageNum int `bson:"ImageNum" json:"-"` // 图片数量 - ImageSize int `bson:"ImageSize" json:"-"` // 图片大小 - AttachNum int `bson:"AttachNum" json:"-"` // 附件数量 - AttachSize int `bson:"AttachSize" json:"-"` // 附件大小 - FromUserId bson.ObjectId `FromUserId,omitempty` // 邀请的用户 + + ImageNum int `bson:"ImageNum" json:"-"` // 图片数量 + ImageSize int `bson:"ImageSize" json:"-"` // 图片大小 + AttachNum int `bson:"AttachNum" json:"-"` // 附件数量 + AttachSize int `bson:"AttachSize" json:"-"` // 附件大小 + FromUserId bson.ObjectId `FromUserId,omitempty` // 邀请的用户 AccountType string `bson:"AccountType" json:"-"` // normal(为空), premium AccountStartTime time.Time `bson:"AccountStartTime" json:"-"` // 开始日期 AccountEndTime time.Time `bson:"AccountEndTime" json:"-"` // 结束日期 // 阈值 - MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量 - MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小 - MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量 - MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小 - MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小 + MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量 + MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小 + MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量 + MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小 + MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小 + + // 2015/1/15, 更新序号 + Usn int `Usn` // UpdateSequenceNum , 全局的 + FullSyncBefore time.Time `bson:"FullSyncBefore"` // 需要全量同步的时间, 如果 > 客户端的LastSyncTime, 则需要全量更新 } type UserAccount struct { @@ -59,11 +63,11 @@ type UserAccount struct { AccountStartTime time.Time `bson:"AccountStartTime" json:"-"` // 开始日期 AccountEndTime time.Time `bson:"AccountEndTime" json:"-"` // 结束日期 // 阈值 - MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量 - MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小 - MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量 - MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小 - MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小 + MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量 + MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小 + MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量 + MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小 + MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小 } // 用户与博客信息结合, 公开 @@ -75,6 +79,6 @@ type UserAndBlog struct { BlogTitle string `BlogTitle` // 博客标题 BlogLogo string `BlogLogo` // 博客Logo BlogUrl string `BlogUrl` // 博客链接, 主页 - + BlogUrls // 各个页面 } diff --git a/app/init.go b/app/init.go index 2c74049..6d4501b 100644 --- a/app/init.go +++ b/app/init.go @@ -6,6 +6,7 @@ import ( "github.com/leanote/leanote/app/service" "github.com/leanote/leanote/app/db" "github.com/leanote/leanote/app/controllers" + "github.com/leanote/leanote/app/controllers/api" "github.com/leanote/leanote/app/controllers/admin" "github.com/leanote/leanote/app/controllers/member" _ "github.com/leanote/leanote/app/lea/binder" @@ -373,5 +374,6 @@ func init() { admin.InitService() member.InitService() service.ConfigS.InitGlobalConfigs() - }); -} \ No newline at end of file + api.InitService() + }) +} diff --git a/app/lea/Util.go b/app/lea/Util.go index 3d28047..64ec2e6 100644 --- a/app/lea/Util.go +++ b/app/lea/Util.go @@ -122,9 +122,44 @@ func ReplaceAll(oldStr, pattern, newStr string) string { return string(s) } +// 获取纯文本 +func SubStringHTMLToRaw(param string, length int) (result string) { + if param == "" { + return "" + } + result = "" + n := 0 + var temp rune // 中文问题, 用rune来解决 + rStr := []rune(param) + isCode := false + for i := 0; i < len(rStr); i++ { + temp = rStr[i] + if temp == '<' { + isCode = true + continue + } else if temp == '>' { + isCode = false + result += " "; // 空格 + continue + } + if !isCode { + result += string(temp) + n++ + if n >= length { + break + } + } + } + return +} + +// 获取摘要, HTML func SubStringHTML(param string, length int, end string) string { - // 先取出
占位..
+	if param == "" {
+		return ""
+	}
 	
+	// 先取出
占位..
 	result := ""
 
 	// 1
@@ -196,6 +231,7 @@ func SubStringHTML(param string, length int, end string) string {
 	
 	return result
 }
+
 // 是否是合格的密码
 func IsGoodPwd(pwd string) (bool, string) {
 	if pwd == "" {
@@ -212,7 +248,7 @@ func IsEmail(email string) bool {
 	if email == "" {
 		return false;
 	}
-	ok, _ := regexp.MatchString(`^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[0-9a-zA-Z]{2,3}$`, email)
+	ok, _ := regexp.MatchString(`^([a-zA-Z0-9]+[_|\_|\.|\-]?)*[a-z\-A-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.|\-]?)*[a-zA-Z0-9\-]+\.[0-9a-zA-Z]{2,3}$`, email)
 	return ok
 }
 
diff --git a/app/lea/binder/binder.go b/app/lea/binder/binder.go
index fb06d44..3087478 100644
--- a/app/lea/binder/binder.go
+++ b/app/lea/binder/binder.go
@@ -4,6 +4,7 @@ import (
 	"github.com/revel/revel"
 	"github.com/leanote/leanote/app/info"
 	"github.com/leanote/leanote/app/controllers"
+//	"github.com/leanote/leanote/app/controllers/api"
 	"fmt"
 	"reflect"
 	"strings"
@@ -54,15 +55,34 @@ func nextKey(key string) string {
 	return key[:fieldLen]
 }
 var leanoteStructBinder = revel.Binder{
+	// name == "noteOrContent"
 	Bind: func(params *revel.Params, name string, typ reflect.Type) reflect.Value {
-		result := reflect.New(typ).Elem()
+		result := reflect.New(typ).Elem() // 创建一个该类型的, 然后其field从所有的param去取
 		fieldValues := make(map[string]reflect.Value)
-		for key, _ := range params.Values {
+//		fmt.Println(name)
+		// fmt.Println(typ) // api.NoteFiles
+		// name = files[0], files[1], noteContent
+//		fmt.Println(params.Values)
+		/*
+map[Title:[test1] METHOD:[POST] NotebookId:[54c4f51705fcd14031000002] 
+files[1][FileId]:[] 
+controller:[note] 
+files[1][LocalFileId]:[54c7ae27d98d0329dd000000] files[1][HasBody]:[true] files[0][FileId]:[] files[0][LocalFileId]:[54c7ae855e94ea2dba000000] action:[addNote] Content:[

lifedddddd




] IsBlog:[false] token:[user1] +files[0][HasBody]:[true]] + */ + nameIsSlice := strings.Contains(name, "[") +// fmt.Println(params.Values["files[1]"]) +// fmt.Println(params.Values["Title"]) + for key, _ := range params.Values {// Title, Content, Files // 这里, 如果没有点, 默认就是a. // life +// fmt.Println("key:" + key); // files[0][LocalFileId] +// fmt.Println("name:" + name); // files[0][LocalFileId] var suffix string var noPrefix = false - if !strings.HasPrefix(key, name + ".") { + if nameIsSlice && strings.HasPrefix(key, name) { + suffix = key[len(name)+1:len(key)-1] // files[0][LocalFileId] 去掉 => LocalFileId + } else if !strings.HasPrefix(key, name + ".") { noPrefix = true suffix = key // continue @@ -71,13 +91,18 @@ var leanoteStructBinder = revel.Binder{ // Strip off the prefix. e.g. foo.bar.baz => bar.baz suffix = key[len(name)+1:] } +// fmt.Println(suffix); fieldName := nextKey(suffix) // e.g. bar => "bar", bar.baz => "bar", bar[0] => "bar" +// fmt.Println(fieldName); fieldLen := len(fieldName) if _, ok := fieldValues[fieldName]; !ok { // Time to bind this field. Get it and make sure we can set it. + fieldName = strings.Title(fieldName) // 传过来title, 但struct是Title +// fmt.Println("xx: " + fieldName) fieldValue := result.FieldByName(fieldName) +// fmt.Println(fieldValue) if !fieldValue.IsValid() { continue } @@ -87,8 +112,16 @@ var leanoteStructBinder = revel.Binder{ var boundVal reflect.Value // 没有name前缀 if(noPrefix) { + // life +// fmt.Println("<<") +// fmt.Println(strings.Title(key[:fieldLen])); boundVal = revel.Bind(params, key[:fieldLen], fieldValue.Type()) } else { +// fmt.Println("final") +// fmt.Println(key[:len(name)+1+fieldLen]) // files[0][HasBody + if nameIsSlice { + fieldLen += 1 + } boundVal = revel.Bind(params, key[:len(name)+1+fieldLen], fieldValue.Type()) } fieldValue.Set(boundVal) @@ -116,5 +149,8 @@ func init() { revel.TypeBinders[reflect.TypeOf(info.UserBlogComment{})] = leanoteStructBinder revel.TypeBinders[reflect.TypeOf(info.UserBlogStyle{})] = leanoteStructBinder revel.TypeBinders[reflect.TypeOf(info.Notebook{})] = leanoteStructBinder + revel.TypeBinders[reflect.TypeOf(info.UserAccount{})] = leanoteStructBinder revel.TypeBinders[reflect.TypeOf(controllers.NoteOrContent{})] = leanoteStructBinder + revel.TypeBinders[reflect.TypeOf(info.ApiNote{})] = leanoteStructBinder + revel.TypeBinders[reflect.TypeOf(info.NoteFile{})] = leanoteStructBinder } \ No newline at end of file diff --git a/app/lea/blog/Template.go b/app/lea/blog/Template.go index 7586788..39ae3ee 100644 --- a/app/lea/blog/Template.go +++ b/app/lea/blog/Template.go @@ -179,8 +179,6 @@ func RenderTemplate(name string, args map[string]interface{}, basePath string, i // 将该basePath下的所有文件提出 files := ListDir(basePath) - Log(basePath) - LogJ(files); for _, t := range files { if !strings.Contains(t, ".html") { continue; @@ -215,7 +213,8 @@ func RenderTemplate(name string, args map[string]interface{}, basePath string, i //////////////////// -// 错误显示 +// + type ErrorResult struct { RenderArgs map[string]interface{} @@ -290,13 +289,12 @@ func (r ErrorResult) Apply(req *revel.Request, resp *revel.Response) { r.RenderArgs["Router"] = revel.MainRouter // 不是preview就不要显示错误了 - LogJ(revelError) -// if r.IsPreview { + if r.IsPreview { var b bytes.Buffer out := io.Writer(resp.Out) // out = ioutil.Discard err = tmpl.Execute(&b, r.RenderArgs) resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8") b.WriteTo(out) -// } + } } \ No newline at end of file diff --git a/app/service/AttachService.go b/app/service/AttachService.go index 27a0e16..8649bd2 100644 --- a/app/service/AttachService.go +++ b/app/service/AttachService.go @@ -1,30 +1,47 @@ package service import ( + "github.com/leanote/leanote/app/db" + "github.com/leanote/leanote/app/info" . "github.com/leanote/leanote/app/lea" "github.com/revel/revel" - "github.com/leanote/leanote/app/info" - "github.com/leanote/leanote/app/db" "gopkg.in/mgo.v2/bson" - "time" "os" "strings" + "time" ) type AttachService struct { } // add attach -func (this *AttachService) AddAttach(attach info.Attach) (ok bool, msg string) { +// api调用时, 添加attach之前是没有note的 +// fromApi表示是api添加的, updateNote传过来的, 此时不要incNote's usn, 因为updateNote会inc的 +func (this *AttachService) AddAttach(attach info.Attach, fromApi bool) (ok bool, msg string) { attach.CreatedTime = time.Now() ok = db.Insert(db.Attachs, attach) + note := noteService.GetNoteById(attach.NoteId.Hex()) + + // api调用时, 添加attach之前是没有note的 + var userId string + if note.NoteId != "" { + userId = note.UserId.Hex() + } else { + userId = attach.UploadUserId.Hex() + } + if ok { // 更新笔记的attachs num this.updateNoteAttachNum(attach.NoteId, 1) } - return + if !fromApi { + // 增长note's usn + noteService.IncrNoteUsn(attach.NoteId.Hex(), userId) + } + + return } // 更新笔记的附件个数 @@ -50,17 +67,32 @@ func (this *AttachService) ListAttachs(noteId, userId string) []info.Attach { if !shareService.HasUpdateNotePerm(noteId, userId) { return attachs } - + db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs) - + return attachs } +// api调用, 通过noteIds得到note's attachs, 通过noteId归类返回 +func (this *AttachService) getAttachsByNoteIds(noteIds []bson.ObjectId) map[string][]info.Attach { + attachs := []info.Attach{} + db.ListByQ(db.Attachs, bson.M{"NoteId": bson.M{"$in": noteIds}}, &attachs) + noteAttchs := make(map[string][]info.Attach) + for _, attach := range attachs { + noteId := attach.NoteId.Hex() + if itAttachs, ok := noteAttchs[noteId]; ok { + noteAttchs[noteId] = append(itAttachs, attach) + } else { + noteAttchs[noteId] = []info.Attach{attach} + } + } + return noteAttchs +} + func (this *AttachService) UpdateImageTitle(userId, fileId, title string) bool { return db.UpdateByIdAndUserIdField(db.Files, fileId, userId, "Title", title) } - // Delete note to delete attas firstly func (this *AttachService) DeleteAllAttachs(noteId, userId string) bool { note := noteService.GetNoteById(noteId) @@ -73,7 +105,7 @@ func (this *AttachService) DeleteAllAttachs(noteId, userId string) bool { } return true } - + return false } @@ -93,7 +125,11 @@ func (this *AttachService) DeleteAttach(attachId, userId string) (bool, string) attach.Path = strings.TrimLeft(attach.Path, "/") err := os.Remove(revel.BasePath + "/" + attach.Path) if err == nil { - return true, "delete file error" + // userService.UpdateAttachSize(note.UserId.Hex(), -attach.Size) + // 修改note Usn + noteService.IncrNoteUsn(attach.NoteId.Hex(), userId) + + return true, "delete file success" } return false, "delete file error" } @@ -107,37 +143,37 @@ func (this *AttachService) DeleteAttach(attachId, userId string) (bool, string) // userId是否具有attach的访问权限 func (this *AttachService) GetAttach(attachId, userId string) (attach info.Attach) { if attachId == "" { - return + return } - + attach = info.Attach{} db.Get(db.Attachs, attachId, &attach) path := attach.Path if path == "" { - return + return } - + note := noteService.GetNoteById(attach.NoteId.Hex()) - + // 判断权限 - + // 笔记是否是公开的 if note.IsBlog { - return + return } - + // 笔记是否是我的 if note.UserId.Hex() == userId { - return + return } - + // 我是否有权限查看或协作 if shareService.HasReadNotePerm(attach.NoteId.Hex(), userId) { - return + return } - + attach = info.Attach{} - return + return } // 复制笔记时需要复制附件 @@ -145,31 +181,58 @@ func (this *AttachService) GetAttach(attachId, userId string) (attach info.Attac func (this *AttachService) CopyAttachs(noteId, toNoteId, toUserId string) bool { attachs := []info.Attach{} db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs) - + // 复制之 toNoteIdO := bson.ObjectIdHex(toNoteId) for _, attach := range attachs { attach.AttachId = "" attach.NoteId = toNoteIdO - + // 文件复制一份 _, ext := SplitFilename(attach.Name) newFilename := NewGuid() + ext dir := "files/" + toUserId + "/attachs" filePath := dir + "/" + newFilename - err := os.MkdirAll(revel.BasePath + "/" + dir, 0755) + err := os.MkdirAll(revel.BasePath+"/"+dir, 0755) if err != nil { return false } - _, err = CopyFile(revel.BasePath + "/" + attach.Path, revel.BasePath + "/" + filePath) + _, err = CopyFile(revel.BasePath+"/"+attach.Path, revel.BasePath+"/"+filePath) if err != nil { return false } attach.Name = newFilename attach.Path = filePath - - this.AddAttach(attach) + + this.AddAttach(attach, false) } - + return true -} \ No newline at end of file +} + +// 只留下files的数据, 其它的都删除 +func (this *AttachService) UpdateOrDeleteAttachApi(noteId, userId string, files []info.NoteFile) bool { + // 现在数据库内的 + attachs := this.ListAttachs(noteId, userId) + + nowAttachs := map[string]bool{} + if files != nil { + for _, file := range files { + if file.IsAttach && file.FileId != "" { + nowAttachs[file.FileId] = true + } + } + } + + for _, attach := range attachs { + fileId := attach.AttachId.Hex() + if !nowAttachs[fileId] { + // 需要删除的 + // TODO 权限验证去掉 + this.DeleteAttach(fileId, userId) + } + } + + return false + +} diff --git a/app/service/AuthService.go b/app/service/AuthService.go index 3937741..be6310c 100644 --- a/app/service/AuthService.go +++ b/app/service/AuthService.go @@ -51,6 +51,7 @@ func (this *AuthService) Register(email, pwd, fromUserId string) (bool, string) func (this *AuthService) register(user info.User) (bool, string) { if userService.AddUser(user) { // 添加笔记本, 生活, 学习, 工作 + userId := user.UserId.Hex(); notebook := info.Notebook{ Seq: -1, UserId: user.UserId} @@ -62,8 +63,6 @@ func (this *AuthService) register(user info.User) (bool, string) { notebookService.AddNotebook(notebook); } - email := user.Email - // 添加leanote -> 该用户的共享 registerSharedUserId := configService.GetGlobalStringConfig("registerSharedUserId") if(registerSharedUserId != "") { @@ -74,20 +73,23 @@ func (this *AuthService) register(user info.User) (bool, string) { // 添加共享笔记本 for _, notebook := range registerSharedNotebooks { perm, _ := strconv.Atoi(notebook["perm"]) - shareService.AddShareNotebook(notebook["notebookId"], perm, registerSharedUserId, email); + shareService.AddShareNotebookToUserId(notebook["notebookId"], perm, registerSharedUserId, userId); } // 添加共享笔记 for _, note := range registerSharedNotes { perm, _ := strconv.Atoi(note["perm"]) - shareService.AddShareNote(note["noteId"], perm, registerSharedUserId, email); + shareService.AddShareNoteToUserId(note["noteId"], perm, registerSharedUserId, userId); } // 复制笔记 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) +// Log(noteId) +// Log("Copy") +// LogJ(note) + noteUpdate := bson.M{"IsBlog": false} // 不要是博客 + noteService.UpdateNote(user.UserId.Hex(), note.NoteId.Hex(), noteUpdate, -1) } } @@ -95,7 +97,7 @@ func (this *AuthService) register(user info.User) (bool, string) { // 添加一条userBlog blogService.UpdateUserBlog(info.UserBlog{UserId: user.UserId, Title: user.Username + " 's Blog", - SubTitle: "love leanote!", + SubTitle: "Love Leanote!", AboutMe: "Hello, I am (^_^)", CanComment: true, }) diff --git a/app/service/BlogService.go b/app/service/BlogService.go index 277008e..eb3c439 100644 --- a/app/service/BlogService.go +++ b/app/service/BlogService.go @@ -69,9 +69,14 @@ func (this *BlogService) GetBlogItem(note info.Note) (blog info.BlogItem) { } // 得到用户共享的notebooks +// 3/19 博客不是deleted func (this *BlogService) ListBlogNotebooks(userId string) []info.Notebook { notebooks := []info.Notebook{} - db.ListByQ(db.Notebooks, bson.M{"UserId": bson.ObjectIdHex(userId), "IsBlog": true}, ¬ebooks) + orQ := []bson.M{ + bson.M{"IsDeleted": false}, + bson.M{"IsDeleted": bson.M{"$exists": false}}, + } + db.ListByQ(db.Notebooks, bson.M{"UserId": bson.ObjectIdHex(userId), "IsBlog": true, "$or": orQ}, ¬ebooks) return notebooks } @@ -1094,13 +1099,15 @@ func (this *BlogService) SortSingles(userId string, singleIds []string) (ok bool // 得到用户的博客url func (this *BlogService) GetUserBlogUrl(userBlog *info.UserBlog, username string) string { - if userBlog.Domain != "" && configService.AllowCustomDomain() { - return configService.GetUserUrl(userBlog.Domain) - } else if userBlog.SubDomain != "" { - return configService.GetUserSubUrl(userBlog.SubDomain) - } - if username == "" { - username = userBlog.UserId.Hex() + if userBlog != nil { + if userBlog.Domain != "" && configService.AllowCustomDomain() { + return configService.GetUserUrl(userBlog.Domain) + } else if userBlog.SubDomain != "" { + return configService.GetUserSubUrl(userBlog.SubDomain) + } + if username == "" { + username = userBlog.UserId.Hex() + } } return configService.GetBlogUrl() + "/" + username } diff --git a/app/service/ConfigService.go b/app/service/ConfigService.go index 422190d..1d9d686 100644 --- a/app/service/ConfigService.go +++ b/app/service/ConfigService.go @@ -580,5 +580,5 @@ func (this *ConfigService) HomePageIsAdminsBlog() bool { } func (this *ConfigService) GetVersion() string { - return "1.0-beta2" + return "1.0-beta.4" } diff --git a/app/service/FileService.go b/app/service/FileService.go index d335db7..48b6f3a 100644 --- a/app/service/FileService.go +++ b/app/service/FileService.go @@ -243,5 +243,9 @@ func (this *FileService) CopyImage(userId, fileId, toUserId string) (bool, strin // 是否是我的文件 func (this *FileService) IsMyFile(userId, fileId string) bool { + // 如果有问题会panic + if !bson.IsObjectIdHex(fileId) || !bson.IsObjectIdHex(userId) { + return false; + } return db.Has(db.Files, bson.M{"UserId": bson.ObjectIdHex(userId), "_id": bson.ObjectIdHex(fileId)}) } diff --git a/app/service/NoteImageService.go b/app/service/NoteImageService.go index 5470889..fde855d 100644 --- a/app/service/NoteImageService.go +++ b/app/service/NoteImageService.go @@ -29,6 +29,7 @@ func (this *NoteImageService) GetNoteIds(imageId string) ([]bson.ObjectId) { return nil } +// TODO 这个web可以用, 但api会传来, 不用用了 // 解析内容中的图片, 建立图片与note的关系 // // 图片必须是我的, 不然不添加 @@ -38,20 +39,21 @@ func (this *NoteImageService) UpdateNoteImages(userId, noteId, imgSrc, content s if imgSrc != "" { content = "" + content } - reg, _ := regexp.Compile("outputImage\\?fileId=([a-z0-9A-Z]{24})") + // life 添加getImage + reg, _ := regexp.Compile("(outputImage|getImage)\\?fileId=([a-z0-9A-Z]{24})") find := reg.FindAllStringSubmatch(content, -1) // 查找所有的 - + // 删除旧的 db.DeleteAll(db.NoteImages, bson.M{"NoteId": bson.ObjectIdHex(noteId)}) - + // 添加新的 var fileId string noteImage := info.NoteImage{NoteId: bson.ObjectIdHex(noteId)} hasAdded := make(map[string]bool) if find != nil && len(find) > 0 { for _, each := range find { - if each != nil && len(each) == 2 { - fileId = each[1] + if each != nil && len(each) == 3 { + fileId = each[2] // 现在有两个子表达式了 // 之前没能添加过的 if _, ok := hasAdded[fileId]; !ok { Log(fileId) @@ -105,3 +107,48 @@ func (this *NoteImageService) CopyNoteImages(fromNoteId, fromUserId, newNoteId, return content; } + +// +func (this *NoteImageService) getImagesByNoteIds(noteIds []bson.ObjectId) map[string][]info.File { + noteNoteImages := []info.NoteImage{} + db.ListByQ(db.NoteImages, bson.M{"NoteId": bson.M{"$in": noteIds}}, ¬eNoteImages) + + // 得到imageId, 再去files表查所有的Files + imageIds := []bson.ObjectId{} + + // 图片1 => N notes + imageIdNotes := map[string][]string{} // imageId => [noteId1, noteId2, ...] + for _, noteImage := range noteNoteImages { + imageId := noteImage.ImageId + imageIds = append(imageIds, imageId) + + imageIdHex := imageId.Hex() + noteId := noteImage.NoteId.Hex() + if notes, ok := imageIdNotes[imageIdHex]; ok { + imageIdNotes[imageIdHex] = append(notes, noteId) + } else { + imageIdNotes[imageIdHex] = []string{noteId} + } + } + + // 得到所有files + files := []info.File{} + db.ListByQ(db.Files, bson.M{"_id": bson.M{"$in": imageIds}}, &files) + + // 建立note->file关联 + noteImages := make(map[string][]info.File) + for _, file := range files { + fileIdHex := file.FileId.Hex() // == imageId + // 这个fileIdHex有哪些notes呢? + if notes, ok := imageIdNotes[fileIdHex]; ok { + for _, noteId := range notes { + if files, ok2 := noteImages[noteId]; ok2 { + noteImages[noteId] = append(files, file) + } else { + noteImages[noteId] = []info.File{file} + } + } + } + } + return noteImages +} diff --git a/app/service/NoteService.go b/app/service/NoteService.go index a169abe..fa50152 100644 --- a/app/service/NoteService.go +++ b/app/service/NoteService.go @@ -44,14 +44,115 @@ func (this *NoteService) GetNoteAndContent(noteId, userId string) (noteAndConten return info.NoteAndContent{note, noteContent} } +// 获取同步的笔记 +// > afterUsn的笔记 +func (this *NoteService) GetSyncNotes(userId string, afterUsn, maxEntry int) []info.ApiNote { + notes := []info.Note{} + q := db.Notes.Find(bson.M{ + "UserId": bson.ObjectIdHex(userId), + "Usn": bson.M{"$gt": afterUsn}, + }); + q.Sort("Usn").Limit(maxEntry).All(¬es) + + return this.ToApiNotes(notes) +} + +// note与apiNote的转换 +func (this *NoteService) ToApiNotes(notes []info.Note) []info.ApiNote { + // 2, 得到所有图片, 附件信息 + // 查images表, attachs表 + if len(notes) > 0 { + noteIds := make([]bson.ObjectId, len(notes)); + for i, note := range notes { + noteIds[i] = note.NoteId + } + noteFilesMap := this.getFiles(noteIds) + // 生成info.ApiNote + apiNotes := make([]info.ApiNote, len(notes)) + for i, note := range notes { + noteId := note.NoteId.Hex() + apiNotes[i] = this.ToApiNote(¬e, noteFilesMap[noteId]) + } + return apiNotes + } + // 返回空的 + return []info.ApiNote{} +} + + +// note与apiNote的转换 +func (this *NoteService) ToApiNote(note *info.Note, files []info.NoteFile) info.ApiNote { + apiNote := info.ApiNote{ + NoteId: note.NoteId.Hex(), + NotebookId: note.NotebookId.Hex(), + UserId : note.UserId.Hex(), + Title : note.Title, + Tags : note.Tags, + IsMarkdown : note.IsMarkdown, + IsBlog : note.IsBlog, + IsTrash : note.IsTrash, + IsDeleted : note.IsDeleted, + Usn : note.Usn, + CreatedTime : note.CreatedTime, + UpdatedTime : note.UpdatedTime, + PublicTime : note.PublicTime, + Files: files, + } + return apiNote +} + +// getDirtyNotes, 把note的图片, 附件信息都发送给客户端 +// 客户端保存到本地, 再获取图片, 附件 + +// 得到所有图片, 附件信息 +// 查images表, attachs表 +// [待测] +func (this *NoteService) getFiles(noteIds []bson.ObjectId) map[string][]info.NoteFile { + noteImages := noteImageService.getImagesByNoteIds(noteIds); + noteAttachs := attachService.getAttachsByNoteIds(noteIds) + + noteFilesMap := map[string][]info.NoteFile{} + + for _, noteId := range noteIds { + noteIdHex := noteId.Hex() + noteFiles := []info.NoteFile{} + // images + if images, ok := noteImages[noteIdHex]; ok { + for _, image := range images { + noteFiles = append(noteFiles, info.NoteFile{ + FileId: image.FileId.Hex(), + Type: image.Type, + }); + } + } + + // attach + if attachs, ok := noteAttachs[noteIdHex]; ok { + for _, attach := range attachs { + noteFiles = append(noteFiles, info.NoteFile{ + FileId: attach.AttachId.Hex(), + Type: attach.Type, + Title: attach.Title, + IsAttach: true, + }); + } + } + + noteFilesMap[noteIdHex] = noteFiles + } + + return noteFilesMap +} + // 列出note, 排序规则, 还有分页 // CreatedTime, UpdatedTime, title 来排序 func (this *NoteService) ListNotes(userId, notebookId string, isTrash bool, pageNumber, pageSize int, sortField string, isAsc bool, isBlog bool) (count int, notes []info.Note) { notes = []info.Note{} skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, sortField, isAsc) + // 不是trash的 - query := bson.M{"UserId": bson.ObjectIdHex(userId), "IsTrash": isTrash} + query := bson.M{"UserId": bson.ObjectIdHex(userId), "IsTrash": isTrash, "IsDeleted": false} if isBlog { query["IsBlog"] = true } @@ -121,7 +222,7 @@ func (this *NoteService) ListNoteContentByNoteIds(noteIds []bson.ObjectId) (note // 首先要判断Notebook是否是Blog, 是的话设为blog // [ok] -func (this *NoteService) AddNote(note info.Note) info.Note { +func (this *NoteService) AddNote(note info.Note, fromApi bool) info.Note { if(note.NoteId.Hex() == "") { noteId := bson.NewObjectId(); note.NoteId = noteId; @@ -131,14 +232,19 @@ func (this *NoteService) AddNote(note info.Note) info.Note { note.IsTrash = false note.UpdatedUserId = note.UserId note.UrlTitle = GetUrTitle(note.UserId.Hex(), note.Title, "note") + note.Usn = userService.IncrUsn(note.UserId.Hex()) - // 设为blog notebookId := note.NotebookId.Hex() - note.IsBlog = notebookService.IsBlog(notebookId) - if note.IsBlog { + // api会传IsBlog, web不会传 + if !fromApi { note.PublicTime = note.UpdatedTime + // 设为blog + note.IsBlog = notebookService.IsBlog(notebookId) } +// if note.IsBlog { + note.PublicTime = note.UpdatedTime +// } db.Insert(db.Notes, note) @@ -156,7 +262,7 @@ func (this *NoteService) AddSharedNote(note info.Note, myUserId bson.ObjectId) i // 判断我是否有权限添加 if shareService.HasUpdateNotebookPerm(note.UserId.Hex(), myUserId.Hex(), note.NotebookId.Hex()) { note.CreatedUserId = myUserId // 是我给共享我的人创建的 - return this.AddNote(note) + return this.AddNote(note, false) } return info.Note{} } @@ -176,6 +282,49 @@ func (this *NoteService) AddNoteContent(noteContent info.NoteContent) info.NoteC return noteContent; } +// API, abstract, desc需要这里获取 +// 不需要 +/* +func (this *NoteService) AddNoteAndContentApi(note info.Note, noteContent info.NoteContent, myUserId bson.ObjectId) info.Note { + if(note.NoteId.Hex() == "") { + noteId := bson.NewObjectId(); + note.NoteId = noteId; + } + note.CreatedTime = time.Now() + note.UpdatedTime = note.CreatedTime + note.IsTrash = false + note.UpdatedUserId = note.UserId + note.UrlTitle = GetUrTitle(note.UserId.Hex(), note.Title, "note") + note.Usn = userService.IncrUsn(note.UserId.Hex()) + + // desc这里获取 + desc := SubStringHTMLToRaw(noteContent.Content, 50) + note.Desc = desc; + + // 设为blog + 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) + + // 这里, 添加到内容中 + abstract := SubStringHTML(noteContent.Content, 200, "") + noteContent.Abstract = abstract + this.AddNoteContent(noteContent) + + return note +}*/ + // 添加笔记和内容 // 这里使用 info.NoteAndContent 接收? func (this *NoteService) AddNoteAndContentForController(note info.Note, noteContent info.NoteContent, updatedUserId string) info.Note { @@ -198,7 +347,24 @@ func (this *NoteService) AddNoteAndContent(note info.Note, noteContent info.Note if note.UserId != myUserId { note = this.AddSharedNote(note, myUserId) } else { - note = this.AddNote(note) + note = this.AddNote(note, false) + } + if note.NoteId != "" { + this.AddNoteContent(noteContent) + } + return note +} + +func (this *NoteService) AddNoteAndContentApi(note info.Note, noteContent info.NoteContent, myUserId bson.ObjectId) info.Note { + if(note.NoteId.Hex() == "") { + noteId := bson.NewObjectId() + note.NoteId = noteId + } + noteContent.NoteId = note.NoteId + if note.UserId != myUserId { + note = this.AddSharedNote(note, myUserId) + } else { + note = this.AddNote(note, true) } if note.NoteId != "" { this.AddNoteContent(noteContent) @@ -207,19 +373,30 @@ func (this *NoteService) AddNoteAndContent(note info.Note, noteContent info.Note } // 修改笔记 -func (this *NoteService) UpdateNote(userId, updatedUserId, noteId string, needUpdate bson.M) bool { +// 这里没有判断usn +func (this *NoteService) UpdateNote(updatedUserId, noteId string, needUpdate bson.M, usn int) (bool, string, int) { + // 是否存在 + note := this.GetNoteById(noteId) + if note.NoteId == "" { + return false, "notExists", 0 + } + + userId := note.UserId.Hex() // updatedUserId 要修改userId的note, 此时需要判断是否有修改权限 if userId != updatedUserId { if !shareService.HasUpdatePerm(userId, updatedUserId, noteId) { Log("NO AUTH2") - return false + return false, "noAuth", 0 } else { Log("HAS AUTH -----------") } } + if usn > 0 && note.Usn != usn { + return false, "conflict", 0 + } + // 是否已自定义 - note := this.GetNoteById(noteId) if note.IsBlog && note.HasSelfDefined { delete(needUpdate, "ImgSrc") delete(needUpdate, "Desc") @@ -227,8 +404,11 @@ func (this *NoteService) UpdateNote(userId, updatedUserId, noteId string, needUp needUpdate["UpdatedUserId"] = bson.ObjectIdHex(updatedUserId); needUpdate["UpdatedTime"] = time.Now(); + afterUsn := userService.IncrUsn(userId); + needUpdate["Usn"] = afterUsn // 添加tag2 + // TODO 这个tag去掉, 添加tag另外添加, 不要这个 if tags, ok := needUpdate["Tags"]; ok { tagService.AddTagsI(userId, tags) } @@ -236,10 +416,55 @@ func (this *NoteService) UpdateNote(userId, updatedUserId, noteId string, needUp // 是否修改了isBlog // 也要修改noteContents的IsBlog if isBlog, ok := needUpdate["IsBlog"]; ok { - db.UpdateByIdAndUserIdMap(db.NoteContents, noteId, userId, bson.M{"IsBlog": isBlog}) + isBlog2 := isBlog.(bool) + if note.IsBlog != isBlog2 { + db.UpdateByIdAndUserIdMap(db.NoteContents, noteId, userId, bson.M{"IsBlog": isBlog2}) + + // 重新发布成博客 + if !note.IsBlog { + needUpdate["PublicTime"] = needUpdate["UpdatedTime"] + } + } } - return db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, needUpdate) + ok := db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, needUpdate) + if !ok { + return ok, "", 0 + } + + // 重新获取之 + note = this.GetNoteById(noteId) + + hasRecount := false + + // 如果修改了notebookId, 则更新notebookId'count + // 两方的notebook也要修改 + notebookIdI := needUpdate["NotebookId"] + if notebookIdI != nil { + notebookId := notebookIdI.(bson.ObjectId) + if notebookId != "" { + notebookService.ReCountNotebookNumberNotes(note.NotebookId.Hex()) + hasRecount = true + notebookService.ReCountNotebookNumberNotes(notebookId.Hex()) + } + } + + // 不要多次更新, isTrash = false, = true都要重新统计 + if !hasRecount { + if _, ok := needUpdate["IsTrash"]; ok { + notebookService.ReCountNotebookNumberNotes(note.NotebookId.Hex()) + } + } + + return true, "", afterUsn +} + +// 附件修改, 增加noteIncr +func (this *NoteService) IncrNoteUsn(noteId, userId string) int { + afterUsn := userService.IncrUsn(userId) + db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, + bson.M{"UpdatedTime": time.Now(), "Usn": afterUsn}) + return afterUsn } // 这里要判断权限, 如果userId != updatedUserId, 那么需要判断权限 @@ -254,31 +479,50 @@ func (this *NoteService) UpdateNoteTitle(userId, updatedUserId, noteId, title st } return db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, - bson.M{"UpdatedUserId": bson.ObjectIdHex(updatedUserId), "Title": title, "UpdatedTime": time.Now()}) + bson.M{"UpdatedUserId": bson.ObjectIdHex(updatedUserId), "Title": title, "UpdatedTime": time.Now(), "Usn": userService.IncrUsn(userId)}) } // 修改笔记本内容 // [ok] TODO perm未测 -func (this *NoteService) UpdateNoteContent(userId, updatedUserId, noteId, content, abstract string) bool { +// hasBeforeUpdateNote 之前是否更新过note其它信息, 如果有更新, usn不用更新 +// TODO abstract这里生成 +func (this *NoteService) UpdateNoteContent(updatedUserId, noteId, content, abstract string, hasBeforeUpdateNote bool, usn int) (bool, string, int) { + // 是否已自定义 + note := this.GetNoteById(noteId) + if note.NoteId == "" { + return false, "notExists", 0 + } + userId := note.UserId.Hex() // updatedUserId 要修改userId的note, 此时需要判断是否有修改权限 if userId != updatedUserId { if !shareService.HasUpdatePerm(userId, updatedUserId, noteId) { Log("NO AUTH") - return false + return false, "noAuth", 0 } } + // abstract重置 data := bson.M{"UpdatedUserId": bson.ObjectIdHex(updatedUserId), "Content": content, "Abstract": abstract, "UpdatedTime": time.Now()} - // 是否已自定义 - note := this.GetNoteById(noteId) if note.IsBlog && note.HasSelfDefined { delete(data, "Abstract") } + // usn, 修改笔记不可能单独修改内容 + afterUsn := 0 + // 如果之前没有修改note其它信息, 那么usn++ + if !hasBeforeUpdateNote { + // 需要验证 + if usn >= 0 && note.Usn != usn { + return false, "conflict", 0 + } + afterUsn = userService.IncrUsn(userId) + db.UpdateByIdAndUserIdField(db.Notes, noteId, userId, "Usn", usn) + } + if db.UpdateByIdAndUserIdMap(db.NoteContents, noteId, userId, data) { // 这里, 添加历史记录 noteContentHistoryService.AddHistory(noteId, userId, info.EachHistory{UpdatedUserId: bson.ObjectIdHex(updatedUserId), @@ -289,9 +533,9 @@ func (this *NoteService) UpdateNoteContent(userId, updatedUserId, noteId, conten // 更新笔记图片 noteImageService.UpdateNoteImages(userId, noteId, note.ImgSrc, content) - return true + return true, "", afterUsn } - return false + return false, "", 0 } // ????? @@ -305,7 +549,7 @@ func (this *NoteService) updateNoteImages(noteId string, content string) bool { // 更新tags // [ok] [del] func (this *NoteService) UpdateTags(noteId string, userId string, tags []string) bool { - return db.UpdateByIdAndUserIdField(db.Notes, noteId, userId, "Tags", tags) + return db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, bson.M{"Tags": tags, "Usn": userService.IncrUsn(userId)}) } func (this *NoteService) ToBlog(userId, noteId string, isBlog, isTop bool) bool { @@ -323,6 +567,8 @@ func (this *NoteService) ToBlog(userId, noteId string, isBlog, isTop bool) bool } else { noteUpdate["HasSelfDefined"] = false } + noteUpdate["Usn"] = userService.IncrUsn(userId) + ok := db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, noteUpdate) // 重新计算tags go (func() { @@ -342,7 +588,9 @@ func (this *NoteService) MoveNote(noteId, notebookId, userId string) info.Note { re := db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": false, - "NotebookId": bson.ObjectIdHex(notebookId)}}) + "NotebookId": bson.ObjectIdHex(notebookId), + "Usn": userService.IncrUsn(userId), + }}) if re { // 更新blog状态 @@ -364,13 +612,14 @@ func (this *NoteService) MoveNote(noteId, notebookId, userId string) info.Note { // 如果自己的blog状态是true, 不用改变, // 否则, 如果notebookId的blog是true, 则改为true之 // 返回blog状态 +// move, copy时用 func (this *NoteService) updateToNotebookBlog(noteId, notebookId, userId string) bool { if this.IsBlog(noteId) { return true } if notebookService.IsBlog(notebookId) { db.UpdateByIdAndUserId(db.Notes, noteId, userId, - bson.M{"$set": bson.M{"IsBlog": true}}) + bson.M{"$set": bson.M{"IsBlog": true, "PublicTime": time.Now()}}) // life return true } return false @@ -567,7 +816,6 @@ func (this *NoteService) SearchNoteByTags(tags []string, userId string, pageNumb return } - //------------ // 统计 func (this *NoteService) CountNote(userId string) int { @@ -583,4 +831,43 @@ func (this *NoteService) CountBlog(userId string) int { q["UserId"] = bson.ObjectIdHex(userId) } return db.Count(db.Notes, q) -} \ No newline at end of file +} + +// 通过标签来查询 +func (this *NoteService) CountNoteByTag(userId string, tag string) int { + if tag == "" { + return 0 + } + query := bson.M{"UserId": bson.ObjectIdHex(userId), +// "IsTrash": false, + "IsDeleted": false, + "Tags": bson.M{"$in": []string{tag}}} + return db.Count(db.Notes, query) +} + +// 删除tag +// 返回所有note的Usn +func (this *NoteService) UpdateNoteToDeleteTag(userId string, targetTag string) map[string]int { + query := bson.M{"UserId": bson.ObjectIdHex(userId), + "Tags": bson.M{"$in": []string{targetTag}}} + notes := []info.Note{} + db.ListByQ(db.Notes, query, ¬es) + ret := map[string]int{} + for _, note := range notes { + tags := note.Tags + if tags == nil { + continue + } + for i, tag := range tags { + if tag == targetTag { + tags = tags + tags = append(tags[:i], tags[i+1:]...) + break; + } + } + usn := userService.IncrUsn(userId) + db.UpdateByQMap(db.Notes, bson.M{"_id": note.NoteId}, bson.M{"Usn": usn, "Tags": tags}) + ret[note.NoteId.Hex()] = usn + } + return ret +} diff --git a/app/service/NotebookService.go b/app/service/NotebookService.go index 3ec77ea..91a26aa 100644 --- a/app/service/NotebookService.go +++ b/app/service/NotebookService.go @@ -111,12 +111,24 @@ func (this *NotebookService) GetNotebookByUserIdAndUrlTitle(userId, notebookIdOr return notebook } +// 同步的方法 +func (this *NotebookService) GeSyncNotebooks(userId string, afterUsn, maxEntry int) ([]info.Notebook) { + notebooks := []info.Notebook{} + q := db.Notebooks.Find(bson.M{"UserId": bson.ObjectIdHex(userId), "Usn": bson.M{"$gt": afterUsn}}); + q.Sort("Usn").Limit(maxEntry).All(¬ebooks) + return notebooks +} + // 得到用户下所有的notebook // 排序好之后返回 // [ok] func (this *NotebookService) GetNotebooks(userId string) info.SubNotebooks { userNotebooks := []info.Notebook{} - db.Notebooks.Find(bson.M{"UserId": bson.ObjectIdHex(userId)}).All(&userNotebooks) + orQ := []bson.M{ + bson.M{"IsDeleted": false}, + bson.M{"IsDeleted": bson.M{"$exists": false}}, + } + db.Notebooks.Find(bson.M{"UserId": bson.ObjectIdHex(userId), "$or": orQ}).All(&userNotebooks) if len(userNotebooks) == 0 { return nil @@ -141,14 +153,46 @@ func (this *NotebookService) GetNotebooksByNotebookIds(notebookIds []bson.Object // 添加 // [ok] -func (this *NotebookService) AddNotebook(notebook info.Notebook) bool { +func (this *NotebookService) AddNotebook(notebook info.Notebook) (bool, info.Notebook) { notebook.UrlTitle = GetUrTitle(notebook.UserId.Hex(), notebook.Title, "notebook") + notebook.Usn = userService.IncrUsn(notebook.UserId.Hex()) + now := time.Now() + notebook.CreatedTime = now + notebook.UpdatedTime = now err := db.Notebooks.Insert(notebook) if err != nil { - panic(err) - } else { + return false, notebook } - return true + return true, notebook +} +// 更新笔记, api +func (this *NotebookService) UpdateNotebookApi(userId, notebookId, title, parentNotebookId string, seq, usn int) (bool, string, info.Notebook) { + if notebookId == "" { + return false, "notebookIdNotExists", info.Notebook{} + } + + // 先判断usn是否和数据库的一样, 如果不一样, 则冲突, 不保存 + notebook := this.GetNotebookById(notebookId) + // 不存在 + if notebook.NotebookId == "" { + return false, "notExists", notebook + } else if notebook.Usn != usn { + return false, "conflict", notebook + } + notebook.Usn = userService.IncrUsn(userId); + notebook.Title = title; + + updates := bson.M{"Title": title, "Usn": notebook.Usn, "Seq": seq, "UpdatedTime": time.Now()}; + if(parentNotebookId != "" && bson.IsObjectIdHex(parentNotebookId)) { + updates["ParentNotebookId"] = bson.ObjectIdHex(parentNotebookId); + } else { + updates["ParentNotebookId"] = ""; + } + ok := db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, updates); + if ok { + return ok, "", this.GetNotebookById(notebookId) + } + return false, "", notebook } // 判断是否是blog @@ -174,19 +218,22 @@ func (this *NotebookService) UpdateNotebook(notebook info.Notebook) bool { // 更新笔记本标题 // [ok] func (this *NotebookService) UpdateNotebookTitle(notebookId, userId, title string) bool { - return db.UpdateByIdAndUserIdField(db.Notebooks, notebookId, userId, "Title", title) + usn := userService.IncrUsn(userId) + return db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, bson.M{"Title": title, "Usn": usn}) } // 更新notebook func (this *NotebookService) UpdateNotebook(userId, notebookId string, needUpdate bson.M) bool { needUpdate["UpdatedTime"] = time.Now(); + needUpdate["Usn"] = userService.IncrUsn(userId) return db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, needUpdate) } // ToBlog or Not func (this *NotebookService) ToBlog(userId, notebookId string, isBlog bool) (bool) { + updates := bson.M{"IsBlog": isBlog, "Usn": userService.IncrUsn(userId)} // 笔记本 - db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, bson.M{"IsBlog": isBlog}) + db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, updates) // 更新笔记 q := bson.M{"UserId": bson.ObjectIdHex(userId), @@ -197,6 +244,8 @@ func (this *NotebookService) ToBlog(userId, notebookId string, isBlog bool) (boo } else { data["HasSelfDefined"] = false } + // usn + data["Usn"] = userService.IncrUsn(userId) db.UpdateByQMap(db.Notes, q, data) // noteContents也更新, 这个就麻烦了, noteContents表没有NotebookId @@ -227,7 +276,10 @@ func (this *NotebookService) DeleteNotebook(userId, notebookId string) (bool, st if db.Count(db.Notes, bson.M{"NotebookId": bson.ObjectIdHex(notebookId), "UserId": bson.ObjectIdHex(userId), "IsTrash": false}) == 0 { // 不包含trash - return db.DeleteByIdAndUserId(db.Notebooks, notebookId, userId), "" + // 不是真删除 1/20, 为了同步笔记本 + ok := db.UpdateByQMap(db.Notebooks, bson.M{"_id": bson.ObjectIdHex(notebookId)}, bson.M{"IsDeleted": true, "Usn": userService.IncrUsn(userId)}) + return ok, "" +// return db.DeleteByIdAndUserId(db.Notebooks, notebookId, userId), "" } return false, "笔记本下有笔记" } else { @@ -235,6 +287,18 @@ func (this *NotebookService) DeleteNotebook(userId, notebookId string) (bool, st } } +// API调用, 删除笔记本, 不作笔记控制 +func (this *NotebookService) DeleteNotebookForce(userId, notebookId string, usn int) (bool, string) { + notebook := this.GetNotebookById(notebookId) + // 不存在 + if notebook.NotebookId == "" { + return false, "notExists" + } else if notebook.Usn != usn { + return false, "conflict" + } + return db.DeleteByIdAndUserId(db.Notebooks, notebookId, userId), "" +} + // 排序 // 传入 notebookId => Seq // 为什么要传入userId, 防止修改其它用户的信息 (恶意) @@ -245,7 +309,7 @@ func (this *NotebookService) SortNotebooks(userId string, notebookId2Seqs map[st } for notebookId, seq := range notebookId2Seqs { - if !db.UpdateByIdAndUserIdField2(db.Notebooks, bson.ObjectIdHex(notebookId), bson.ObjectIdHex(userId), "Seq", seq) { + if !db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, bson.M{"Seq": seq, "Usn": userService.IncrUsn(userId)}) { return false } } @@ -253,15 +317,14 @@ func (this *NotebookService) SortNotebooks(userId string, notebookId2Seqs map[st return true } +// 排序和设置父 func (this *NotebookService) DragNotebooks(userId string, curNotebookId string, parentNotebookId string, siblings []string) bool { - userIdO := bson.ObjectIdHex(userId) - ok := false // 如果没parentNotebookId, 则parentNotebookId设空 if(parentNotebookId == "") { - ok = db.UpdateByIdAndUserIdField2(db.Notebooks, bson.ObjectIdHex(curNotebookId), userIdO, "ParentNotebookId", ""); + ok = db.UpdateByIdAndUserIdMap(db.Notebooks, curNotebookId, userId, bson.M{"ParentNotebookId": "", "Usn": userService.IncrUsn(userId)}); } else { - ok = db.UpdateByIdAndUserIdField2(db.Notebooks, bson.ObjectIdHex(curNotebookId), userIdO, "ParentNotebookId", bson.ObjectIdHex(parentNotebookId)); + ok = db.UpdateByIdAndUserIdMap(db.Notebooks, curNotebookId, userId, bson.M{"ParentNotebookId": bson.ObjectIdHex(parentNotebookId), "Usn": userService.IncrUsn(userId)}); } if !ok { @@ -270,7 +333,7 @@ func (this *NotebookService) DragNotebooks(userId string, curNotebookId string, // 排序 for seq, notebookId := range siblings { - if !db.UpdateByIdAndUserIdField2(db.Notebooks, bson.ObjectIdHex(notebookId), userIdO, "Seq", seq) { + if !db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, bson.M{"Seq": seq, "Usn": userService.IncrUsn(userId)}) { return false } } @@ -283,7 +346,7 @@ func (this *NotebookService) DragNotebooks(userId string, curNotebookId string, // 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}) + count := db.Count(db.Notes, bson.M{"NotebookId": notebookIdO, "IsTrash": false, "IsDeleted": false}) Log(count) Log(notebookId) return db.UpdateByQField(db.Notebooks, bson.M{"_id": notebookIdO}, "NumberNotes", count) diff --git a/app/service/SessionService.go b/app/service/SessionService.go index 6156790..e395c52 100644 --- a/app/service/SessionService.go +++ b/app/service/SessionService.go @@ -69,3 +69,20 @@ func (this *SessionService) SetCaptcha(sessionId, captcha string) bool { Log(ok) return ok } + +//----------- +// API +func (this *SessionService) GetUserId(sessionId string) string { + session := this.Get(sessionId) + // 更新updateTime, 避免过期 + db.UpdateByQMap(db.Sessions, bson.M{"SessionId": sessionId}, + bson.M{"UpdatedTime": time.Now()}) + return session.UserId +} + +// 登录成功后设置userId +func (this *SessionService) SetUserId(sessionId, userId string) bool { + this.Get(sessionId) + ok := this.Update(sessionId, "UserId", userId) + return ok +} diff --git a/app/service/ShareService.go b/app/service/ShareService.go index d1c05bb..5f188e8 100644 --- a/app/service/ShareService.go +++ b/app/service/ShareService.go @@ -294,7 +294,11 @@ func (this *ShareService) AddShareNotebook(notebookId string, perm int, userId, if toUserId == "" { return false, "无该用户", "" } + return this.AddShareNotebookToUserId(notebookId, perm, userId, toUserId) +} +// 第三方注册时没有email +func (this *ShareService) AddShareNotebookToUserId(notebookId string, perm int, userId, toUserId string) (bool, string, string) { // 添加一条记录说明两者存在关系 this.AddHasShareNote(userId, toUserId); @@ -327,7 +331,11 @@ func (this *ShareService) AddShareNote(noteId string, perm int, userId, email st if toUserId == "" { return false, "无该用户", "" } - + return this.AddShareNoteToUserId(noteId, perm, userId, toUserId) +} + +// 第三方测试没有userId +func (this *ShareService) AddShareNoteToUserId(noteId string, perm int, userId, toUserId string) (bool, string, string) { // 添加一条记录说明两者存在关系 this.AddHasShareNote(userId, toUserId); diff --git a/app/service/TagService.go b/app/service/TagService.go index d6ecc28..88f799b 100644 --- a/app/service/TagService.go +++ b/app/service/TagService.go @@ -3,9 +3,9 @@ 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" + "time" ) /* @@ -14,12 +14,14 @@ import ( type TagService struct { } +/* func (this *TagService) GetTags(userId string) []string { tag := info.Tag{} db.Get(db.Tags, userId, &tag) LogJ(tag) return tag.Tags } +*/ func (this *TagService) AddTagsI(userId string, tags interface{}) bool { if ts, ok2 := tags.([]string); ok2 { @@ -36,4 +38,100 @@ func (this *TagService) AddTags(userId string, tags []string) bool { } } return true -} \ No newline at end of file +} + +//--------------------------- +// v2 +// 第二版标签, 单独一张表, 每一个tag一条记录 + +// 添加或更新标签, 先查下是否存在, 不存在则添加, 存在则更新 +// 都要统计下tag的note数 +// 什么时候调用? 笔记添加Tag, 删除Tag时 +// 删除note时, 都可以调用 +// 万能 +func (this *TagService) AddOrUpdateTag(userId string, tag string) info.NoteTag { + userIdO := bson.ObjectIdHex(userId) + noteTag := info.NoteTag{} + db.GetByQ(db.NoteTags, bson.M{"UserId": userIdO, "Tag": tag}, ¬eTag) + + // 存在, 则更新之 + if noteTag.TagId != "" { + // 统计note数 + count := noteService.CountNoteByTag(userId, tag) + noteTag.Count = count + noteTag.UpdatedTime = time.Now() +// noteTag.Usn = userService.IncrUsn(userId), 更新count而已 + db.UpdateByIdAndUserId(db.NoteTags, noteTag.TagId.Hex(), userId, noteTag) + return noteTag + } + + // 不存在, 则创建之 + noteTag.TagId = bson.NewObjectId() + noteTag.Count = 1 + noteTag.Tag = tag + noteTag.UserId = bson.ObjectIdHex(userId) + noteTag.CreatedTime = time.Now() + noteTag.UpdatedTime = noteTag.CreatedTime + noteTag.Usn = userService.IncrUsn(userId) + noteTag.IsDeleted = false + db.Insert(db.NoteTags, noteTag) + + return noteTag +} + +// 得到标签, 按更新时间来排序 +func (this *TagService) GetTags(userId string) []info.NoteTag { + tags := []info.NoteTag{} + query := bson.M{"UserId": bson.ObjectIdHex(userId), "IsDeleted": false} + q := db.NoteTags.Find(query); + sortFieldR := "-UpdatedTime" + q.Sort(sortFieldR).All(&tags) + return tags +} + +// 删除标签 +// 也删除所有的笔记含该标签的 +// 返回noteId => usn +func (this *TagService) DeleteTag(userId string, tag string) map[string]int { + usn := userService.IncrUsn(userId) + if db.UpdateByQMap(db.NoteTags, bson.M{"UserId": bson.ObjectIdHex(userId), "Tag": tag}, bson.M{"Usn": usn, "IsDeleted": true}) { + return noteService.UpdateNoteToDeleteTag(userId, tag); + } + return map[string]int{} +} + +// 删除标签, 供API调用 +func (this *TagService) DeleteTagApi(userId string, tag string, usn int) (ok bool, msg string, toUsn int) { + noteTag := info.NoteTag{} + db.GetByQ(db.NoteTags, bson.M{"UserId": bson.ObjectIdHex(userId), "Tag": tag}, ¬eTag) + + if noteTag.TagId == "" { + return false, "notExists", 0 + } + if noteTag.Usn > usn { + return false, "conflict", 0 + } + toUsn = userService.IncrUsn(userId) + if db.UpdateByQMap(db.NoteTags, bson.M{"UserId": bson.ObjectIdHex(userId), "Tag": tag}, bson.M{"Usn": usn, "IsDeleted": true}) { + return true, "", toUsn + } + return false, "", 0 +} + +// 重新统计标签的count +func (this *TagService) reCountTagCount(userId string, tags []string) { + if tags == nil { + return + } + for _, tag := range tags { + this.AddOrUpdateTag(userId, tag); + } +} + +// 同步用 +func (this *TagService) GeSyncTags(userId string, afterUsn, maxEntry int) ([]info.NoteTag) { + noteTags := []info.NoteTag{} + q := db.NoteTags.Find(bson.M{"UserId": bson.ObjectIdHex(userId), "Usn": bson.M{"$gt": afterUsn}}); + q.Sort("Usn").Limit(maxEntry).All(¬eTags) + return noteTags +} diff --git a/app/service/TrashService.go b/app/service/TrashService.go index f92d756..0cc92ed 100644 --- a/app/service/TrashService.go +++ b/app/service/TrashService.go @@ -28,7 +28,7 @@ func (this *TrashService) DeleteNote(noteId, userId string) bool { // 首先删除其共享 if shareService.DeleteShareNoteAll(noteId, userId) { // 更新note isTrash = true - if 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, "Usn": userService.IncrUsn(userId)}}) { // recount notebooks' notes number notebookIdO := noteService.GetNotebookId(noteId) notebookId := notebookIdO.Hex() @@ -44,7 +44,7 @@ func (this *TrashService) DeleteNote(noteId, userId string) bool { func (this *TrashService) DeleteSharedNote(noteId, userId, myUserId string) bool { note := noteService.GetNote(noteId, userId) if shareService.HasUpdatePerm(userId, myUserId, noteId) && note.CreatedUserId.Hex() == myUserId { - return db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": true}}) + return db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": true, "Usn": userService.IncrUsn(userId)}}) } return false } @@ -53,23 +53,63 @@ func (this *TrashService) DeleteSharedNote(noteId, userId, myUserId string) bool func (this *TrashService) recoverNote(noteId, notebookId, userId string) bool { re := db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": false, + "Usn": userService.IncrUsn(userId), "NotebookId": bson.ObjectIdHex(notebookId)}}) return re; } // 删除trash func (this *TrashService) DeleteTrash(noteId, userId string) bool { + note := noteService.GetNote(noteId, userId); + if note.NoteId == "" { + return false + } // delete note's attachs ok := attachService.DeleteAllAttachs(noteId, userId) - + + // 设置删除位 + ok = db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, + bson.M{"IsDeleted": true, + "Usn": userService.IncrUsn(userId)}) // delete note - ok = db.DeleteByIdAndUserId(db.Notes, noteId, userId) +// ok = db.DeleteByIdAndUserId(db.Notes, noteId, userId) + // delete content ok = db.DeleteByIdAndUserId(db.NoteContents, noteId, userId) + // 重新统计tag's count + // TODO 这里会改变tag's Usn + tagService.reCountTagCount(userId, note.Tags) + return ok } +func (this *TrashService) DeleteTrashApi(noteId, userId string, usn int) (bool, string, int) { + note := noteService.GetNote(noteId, userId) + + if note.NoteId == "" || note.IsDeleted { + return false, "notExists", 0 + } + + if note.Usn != usn { + return false, "conflict", 0 + } + + // delete note's attachs + ok := attachService.DeleteAllAttachs(noteId, userId) + + // 设置删除位 + afterUsn := userService.IncrUsn(userId) + ok = db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, + bson.M{"IsDeleted": true, + "Usn": afterUsn}) + + // delete content + ok = db.DeleteByIdAndUserId(db.NoteContents, noteId, userId) + + return ok, "", afterUsn +} + // 列出note, 排序规则, 还有分页 // CreatedTime, UpdatedTime, title 来排序 func (this *TrashService) ListNotes(userId string, diff --git a/app/service/UpgradeService.go b/app/service/UpgradeService.go index a595dc2..77c5f4d 100644 --- a/app/service/UpgradeService.go +++ b/app/service/UpgradeService.go @@ -5,7 +5,7 @@ import ( . "github.com/leanote/leanote/app/lea" "github.com/leanote/leanote/app/db" "gopkg.in/mgo.v2/bson" -// "time" + "time" ) @@ -38,7 +38,7 @@ func (this *UpgradeService) UpgradeBlog() bool { */ func (this *UpgradeService) UpgradeBetaToBeta2(userId string) (ok bool, msg string) { if configService.GetGlobalStringConfig("UpgradeBetaToBeta2") != "" { - return false, "已升级" + return false, "Leanote have been upgraded" } // 1. aboutMe -> page @@ -102,3 +102,81 @@ func (this *UpgradeService) UpgradeBetaToBeta2(userId string) (ok bool, msg stri return } + +// Usn设置 +// 客户端 api + +func (this *UpgradeService) moveTag() { + usnI := 1 + tags := []info.Tag{} + db.ListByQ(db.Tags, bson.M{}, &tags) + for _, eachTag := range tags { + tagTitles := eachTag.Tags + now := time.Now() + if tagTitles != nil && len(tagTitles) > 0 { + for _, tagTitle := range tagTitles { + noteTag := info.NoteTag{} + noteTag.TagId = bson.NewObjectId() + noteTag.Count = 1 + noteTag.Tag = tagTitle + noteTag.UserId = eachTag.UserId + noteTag.CreatedTime = now + noteTag.UpdatedTime = now + noteTag.Usn = usnI + noteTag.IsDeleted = false + db.Insert(db.NoteTags, noteTag) + usnI++ + } + } + } +} + +func (this *UpgradeService) setNotebookUsn() { + usnI := 1 + notebooks := []info.Notebook{} + db.ListByQWithFields(db.Notebooks, bson.M{}, []string{"UserId"}, ¬ebooks) + + for _, notebook := range notebooks { + db.UpdateByQField(db.Notebooks, bson.M{"_id": notebook.NotebookId}, "Usn", usnI) + usnI++ + } +} + +func (this *UpgradeService) setNoteUsn() { + usnI := 1 + notes := []info.Note{} + db.ListByQWithFields(db.Notes, bson.M{}, []string{"UserId"}, ¬es) + + for _, note := range notes { + db.UpdateByQField(db.Notes, bson.M{"_id": note.NoteId}, "Usn", usnI) + usnI++ + } +} + +// 升级为Api, beta.4 +func (this *UpgradeService) Api(userId string) (ok bool, msg string) { + if configService.GetGlobalStringConfig("UpgradeBetaToBeta4") != "" { + return false, "Leanote have been upgraded" + } + + // user + db.UpdateByQField(db.Users, bson.M{}, "Usn", 200000) + + // notebook + db.UpdateByQField(db.Notebooks, bson.M{}, "IsDeleted", false) + this.setNotebookUsn(); + + // note + // 1-N + db.UpdateByQField(db.Notes, bson.M{}, "IsDeleted", false) + this.setNoteUsn(); + + // tag + // 1-N + /// tag, 要重新插入, 将之前的Tag表迁移到NoteTag中 + this.moveTag(); + + configService.UpdateGlobalStringConfig(userId, "UpgradeBetaToBeta4", "1") + + return true, "" +} diff --git a/app/service/UserService.go b/app/service/UserService.go index 9577a6b..d51c014 100644 --- a/app/service/UserService.go +++ b/app/service/UserService.go @@ -12,6 +12,27 @@ import ( type UserService struct { } +// 自增Usn +// 每次notebook,note添加, 修改, 删除, 都要修改 +func (this *UserService) IncrUsn(userId string) int { + user := info.User{} + query := bson.M{"_id": bson.ObjectIdHex(userId)} + db.GetByQWithFields(db.Users, query, []string{"Usn"}, &user) + usn := user.Usn + usn += 1 + Log("inc Usn") + db.UpdateByQField(db.Users, query, "Usn", usn) + return usn +// return db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"ReadNum": 1}}) +} + +func (this *UserService) GetUsn(userId string) int { + user := info.User{} + query := bson.M{"_id": bson.ObjectIdHex(userId)} + db.GetByQWithFields(db.Users, query, []string{"Usn"}, &user) + return user.Usn +} + // 添加用户 func (this *UserService) AddUser(user info.User) bool { if user.UserId == "" { @@ -98,6 +119,8 @@ func (this *UserService) GetUserInfo(userId string) info.User { func (this *UserService) GetUserInfoByEmail(email string) info.User { user := info.User{} db.GetByQ(db.Users, bson.M{"Email": email}, &user) + // Logo路径问题, 有些有http: 有些没有 + this.setUserLogo(&user) return user } // 得到用户信息 username @@ -105,12 +128,15 @@ func (this *UserService) GetUserInfoByUsername(username string) info.User { user := info.User{} username = strings.ToLower(username) db.GetByQ(db.Users, bson.M{"Username": username}, &user) + // Logo路径问题, 有些有http: 有些没有 + this.setUserLogo(&user) return user } func (this *UserService) GetUserInfoByThirdUserId(thirdUserId string) info.User { user := info.User{} db.GetByQ(db.Users, bson.M{"ThirdUserId": thirdUserId}, &user) + this.setUserLogo(&user) return user } func (this *UserService) ListUserInfosByUserIds(userIds []bson.ObjectId) []info.User { @@ -223,7 +249,7 @@ func (this *UserService) LoginGetUserInfo(emailOrUsername, md5Pwd string) info.U } else { db.GetByQ(db.Users, bson.M{"Username": emailOrUsername, "Pwd": md5Pwd}, &user) } - + this.setUserLogo(&user) return user } diff --git a/app/views/admin/nav.html b/app/views/admin/nav.html index 11af6f4..1123eb9 100644 --- a/app/views/admin/nav.html +++ b/app/views/admin/nav.html @@ -229,7 +229,12 @@
  • - v1.0-beta to v1.0-beta2 + v1.0-beta to v1.0-beta.2 + + + + + v1.0-beta.2/3 to v1.0-beta.4
  • diff --git a/app/views/admin/upgrade/beta2.html b/app/views/admin/upgrade/beta2.html index 8de190c..dc2af5b 100644 --- a/app/views/admin/upgrade/beta2.html +++ b/app/views/admin/upgrade/beta2.html @@ -1,5 +1,5 @@ {{template "admin/top.html" .}} -

    Upgrade v1.0-beta to v1.0-beta2

    +

    Upgrade v1.0-beta to v1.0-beta.2

    @@ -17,7 +17,7 @@
    @@ -31,14 +31,16 @@ $(function() { $("#submit").click(function(e){ e.preventDefault(); var t = this; - $(t).button('loading'); - ajaxPost("/adminUpgrade/UpgradeBetaToBeta2", {}, function(ret){ - $(t).button('reset') - if(!ret.Ok) { - art.alert(ret.Msg) - } else { - art.tips("Success"); - } + art.confirm("Are you sure to upgrade to v1.0-beta.2", function() { + $(t).button('loading'); + ajaxPost("/adminUpgrade/UpgradeBetaToBeta2", {}, function(ret){ + $(t).button('reset') + if(!ret.Ok) { + art.alert(ret.Msg) + } else { + art.tips("Success"); + } + }); }); }); }); diff --git a/app/views/admin/upgrade/beta3.html b/app/views/admin/upgrade/beta3.html new file mode 100644 index 0000000..95d572d --- /dev/null +++ b/app/views/admin/upgrade/beta3.html @@ -0,0 +1,46 @@ +{{template "admin/top.html" .}} +

    Upgrade v1.0-beta.2/3 to v1.0-beta.4

    + +
    + +
    +
    +
    +
    + Current Version: leanote v{{.version}} +
      +
    • Api support (Enable Leanote Desktop App to connect server))
    • +
    • Update tags
    • +
    +
    + +
    +
    +
    + +
    + +{{template "admin/footer.html" .}} + + +{{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/home/header.html b/app/views/home/header.html index 74fed89..566d1c7 100644 --- a/app/views/home/header.html +++ b/app/views/home/header.html @@ -44,6 +44,7 @@ function log(o) { -->
  • {{msg . "download"}}
  • {{msg . "donate"}}
  • +
  • lea++
  • {{msg . "discussion"}}
    @@ -107,4 +108,4 @@ function log(o) { ---> \ No newline at end of file +--> diff --git a/app/views/note/note-dev.html b/app/views/note/note-dev.html index a79ac1e..5ee69ce 100644 --- a/app/views/note/note-dev.html +++ b/app/views/note/note-dev.html @@ -713,7 +713,7 @@ function log(o) {