diff --git a/.gitignore b/.gitignore index f7308ef..348dea1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,4 @@ app/tmp/main.go .settings .project public/config.codekit - +files diff --git a/app/controllers/AttachController.go b/app/controllers/AttachController.go new file mode 100644 index 0000000..d238c98 --- /dev/null +++ b/app/controllers/AttachController.go @@ -0,0 +1,212 @@ +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" + "io/ioutil" + "os" + "strings" + "time" + "io" + "archive/tar" + "compress/gzip" +) + +// 附件 +type Attach struct { + BaseController +} + +// 上传附件 +func (c Attach) UploadAttach(noteId string) revel.Result { + re := c.uploadAttach(noteId) + return c.RenderJson(re) +} +func (c Attach) uploadAttach(noteId string) (re info.Re) { + var fileId = "" + var resultMsg = "error" // 错误信息 + var Ok = false + var fileInfo info.Attach + + re = info.NewRe() + + defer func() { + re.Id = fileId // 只是id, 没有其它信息 + re.Msg = resultMsg + re.Ok = Ok + re.Item = fileInfo + }() + + // 判断是否有权限为笔记添加附件 + if !shareService.HasUpdateNotePerm(noteId, c.GetUserId()) { + return re + } + + file, handel, err := c.Request.FormFile("file") + if err != nil { + return re + } + defer file.Close() + + data, err := ioutil.ReadAll(file) + if err != nil { + return re + } + // > 5M? + if(len(data) > 5 * 1024 * 1024) { + resultMsg = "File is bigger than 5M" + return re + } + + // 生成上传路径 + filePath := "files/" + c.GetUserId() + "/attachs" + dir := revel.BasePath + "/" + filePath + err = os.MkdirAll(dir, 0755) + if err != nil { + return re + } + // 生成新的文件名 + filename := handel.Filename + _, ext := SplitFilename(filename) // .doc + filename = NewGuid() + ext + toPath := dir + "/" + filename; + err = ioutil.WriteFile(toPath, data, 0777) + if err != nil { + return re + } + + // add File to db + fileType := "" + if ext != "" { + fileType = strings.ToLower(ext[1:]) + } + filesize := GetFilesize(toPath) + fileInfo = info.Attach{Name: filename, + Title: handel.Filename, + NoteId: bson.ObjectIdHex(noteId), + UploadUserId: c.GetObjectUserId(), + Path: filePath + "/" + filename, + Type: fileType, + Size: filesize} + + id := bson.NewObjectId(); + fileInfo.AttachId = id + fileId = id.Hex() + Ok = attachService.AddAttach(fileInfo) + + fileInfo.Path = ""; // 不要返回 + resultMsg = "success" + + return re +} + +// 删除附件 +func (c Attach) DeleteAttach(attachId string) revel.Result { + re := info.NewRe() + re.Ok, re.Msg = attachService.DeleteAttach(attachId, c.GetUserId()) + return c.RenderJson(re) +} + +// get all attachs by noteId +func (c Attach) GetAttachs(noteId string) revel.Result { + re := info.NewRe() + re.Ok = true + re.List = attachService.ListAttachs(noteId, c.GetUserId()) + return c.RenderJson(re) +} + +// 下载附件 +// 权限判断 +func (c Attach) Download(attachId string) revel.Result { + attach := attachService.GetAttach(attachId, c.GetUserId()); // 得到路径 + path := attach.Path + if path == "" { + return c.RenderText("") + } + fn := revel.BasePath + "/" + strings.TrimLeft(path, "/") + file, _ := os.Open(fn) + return c.RenderBinary(file, attach.Title, revel.Attachment, time.Now()) // revel.Attachment + // return c.RenderFile(file, revel.Attachment) // revel.Attachment +} + +func (c Attach) DownloadAll(noteId string) revel.Result { + note := noteService.GetNoteById(noteId) + if note.NoteId == "" { + return c.RenderText("") + } + // 得到文件列表 + attachs := attachService.ListAttachs(noteId, c.GetUserId()) + if attachs == nil || len(attachs) == 0 { + return c.RenderText("") + } + + /* + dir := revel.BasePath + "/files/tmp" + err := os.MkdirAll(dir, 0755) + if err != nil { + return c.RenderText("") + } + */ + + filename := note.Title + ".tar.gz" + if note.Title == "" { + filename = "all.tar.gz" + } + + // 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/FileController.go b/app/controllers/FileController.go index 62a8a35..91cdb6b 100644 --- a/app/controllers/FileController.go +++ b/app/controllers/FileController.go @@ -3,11 +3,13 @@ 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" "io/ioutil" "os" "strconv" + "strings" ) // 首页 @@ -15,7 +17,7 @@ type File struct { BaseController } -// 上传图片 editor +// 过时 已弃用! func (c File) UploadImage(renderHtml string) revel.Result { if renderHtml == "" { renderHtml = "file/image.html" @@ -30,36 +32,60 @@ func (c File) UploadImage(renderHtml string) revel.Result { return c.RenderTemplate(renderHtml) } +// 已弃用 +func (c File) UploadImageJson(from, noteId string) revel.Result { + re := c.uploadImage(from, ""); + return c.RenderJson(re) +} + + // 上传的是博客logo +// TODO logo不要设置权限, 另外的目录 func (c File) UploadBlogLogo() revel.Result { return c.UploadImage("file/blog_logo.html"); } // 拖拉上传, pasteImage -func (c File) UploadImageJson(renderHtml, from string) revel.Result { - re := c.uploadImage(from, ""); - re.Id = siteUrl + re.Id -// re.Id = re.Id +// noteId 是为了判断是否是协作的note, 如果是则需要复制一份到note owner中 +func (c File) PasteImage(noteId string) revel.Result { + re := c.uploadImage("pasteImage", ""); + + userId := c.GetUserId() + note := noteService.GetNoteById(noteId) + if note.UserId != "" { + noteUserId := note.UserId.Hex() + if noteUserId != userId { + // 是否是有权限协作的 + if shareService.HasUpdatePerm(noteUserId, userId, noteId) { + // 复制图片之, 图片复制给noteUserId + _, re.Id = fileService.CopyImage(userId, re.Id, noteUserId) + } else { + // 怎么可能在这个笔记下paste图片呢? + // 正常情况下不会 + } + } + } + return c.RenderJson(re) } -// leaui image plugin +// leaui image plugin upload image func (c File) UploadImageLeaui(albumId string) revel.Result { re := c.uploadImage("", albumId); - re.Id = siteUrl + re.Id -// re.Id = re.Id return c.RenderJson(re) } // 上传图片, 公用方法 +// upload image common func func (c File) uploadImage(from, albumId string) (re info.Re) { var fileUrlPath = "" + var fileId = "" var resultCode = 0 // 1表示正常 var resultMsg = "内部错误" // 错误信息 var Ok = false defer func() { - re.Id = fileUrlPath + re.Id = fileId // 只是id, 没有其它信息 re.Code = resultCode re.Msg = resultMsg re.Ok = Ok @@ -71,11 +97,10 @@ func (c File) uploadImage(from, albumId string) (re info.Re) { } defer file.Close() // 生成上传路径 - fileUrlPath = "/upload/" + c.GetUserId() + "/images" - dir := revel.BasePath + "/public/" + fileUrlPath + fileUrlPath = "files/" + c.GetUserId() + "/images" + dir := revel.BasePath + "/" + fileUrlPath err = os.MkdirAll(dir, 0755) if err != nil { - Log(err) return re } // 生成新的文件名 @@ -126,8 +151,12 @@ func (c File) uploadImage(from, albumId string) (re info.Re) { Path: fileUrlPath, Size: filesize} + id := bson.NewObjectId(); + fileInfo.FileId = id + fileId = id.Hex() Ok = fileService.AddImage(fileInfo, albumId, c.GetUserId()) + fileInfo.Path = ""; // 不要返回 re.Item = fileInfo return re @@ -197,4 +226,27 @@ func (c File) UpgradeLeauiImage() revel.Result { re.Msg = msg return c.RenderJson(re) -} \ No newline at end of file +} + +//----------- + +// 输出image +// 权限判断 +func (c File) OutputImage(noteId, fileId string) revel.Result { + path := fileService.GetFile(c.GetUserId(), fileId); // 得到路径 + if path == "" { + return c.RenderText("") + } + fn := revel.BasePath + "/" + strings.TrimLeft(path, "/") + file, _ := os.Open(fn) + return c.RenderFile(file, revel.Inline) // revel.Attachment +} + +// 协作时复制图片到owner +func (c File) CopyImage(userId, fileId, toUserId string) revel.Result { + re := info.NewRe() + + re.Ok, re.Id = fileService.CopyImage(userId, fileId, toUserId) + + return c.RenderJson(re) +} diff --git a/app/controllers/NoteController.go b/app/controllers/NoteController.go index ac8558c..17b48c5 100644 --- a/app/controllers/NoteController.go +++ b/app/controllers/NoteController.go @@ -117,7 +117,6 @@ type NoteOrContent struct { // 这里不能用json, 要用post func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result { // 新添加note - LogJ(noteOrContent) if noteOrContent.IsNew { userId := c.GetObjectUserId(); myUserId := userId diff --git a/app/controllers/init.go b/app/controllers/init.go index 5736b19..56fb798 100644 --- a/app/controllers/init.go +++ b/app/controllers/init.go @@ -25,6 +25,7 @@ var suggestionService *service.SuggestionService var albumService *service.AlbumService var fileService *service.FileService +var attachService *service.AttachService var pageSize = 1000 var defaultSortField = "UpdatedTime" @@ -56,12 +57,13 @@ var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": tru "User": map[string]bool{"UpdateEmail": true, "ActiveEmail":true, }, - "oauth": map[string]bool{"githubCallback": true}, + "Oauth": map[string]bool{"GithubCallback": true}, + "File": map[string]bool{"OutputImage": true, "OutputFile": true}, } func needValidate(controller, method string) bool { // 在里面 if v, ok := commonUrl[controller]; ok { - // 不在commonUrl里 + // 在commonUrl里 if _, ok2 := v[method]; ok2 { return false } diff --git a/app/db/Mgo.go b/app/db/Mgo.go index 7a330df..6f91f8b 100644 --- a/app/db/Mgo.go +++ b/app/db/Mgo.go @@ -36,6 +36,9 @@ var Suggestions *mgo.Collection // Album & file(image) var Albums *mgo.Collection var Files *mgo.Collection +var Attachs *mgo.Collection + +var NoteImages *mgo.Collection // 初始化时连接数据库 func Init() { @@ -104,6 +107,9 @@ func Init() { // Album & file Albums = Session.DB(dbname).C("albums") Files = Session.DB(dbname).C("files") + Attachs = Session.DB(dbname).C("attachs") + + NoteImages = Session.DB(dbname).C("note_images") } func init() { diff --git a/app/info/AttachInfo.go b/app/info/AttachInfo.go new file mode 100644 index 0000000..4dc39ef --- /dev/null +++ b/app/info/AttachInfo.go @@ -0,0 +1,21 @@ +package info + +import ( + "gopkg.in/mgo.v2/bson" + "time" +) + +// Attach belongs to note +type Attach struct { + AttachId bson.ObjectId `bson:"_id,omitempty"` // + NoteId bson.ObjectId `bson:"NoteId"` // + UploadUserId bson.ObjectId `bson:"UploadUserId"` // 可以不是note owner, 协作者userId + Name string `Name` // file name, md5, such as 13232312.doc + Title string `Title` // raw file name + Size int64 `Size` // file size (byte) + Type string `Type` // file type, "doc" = word + Path string `Path` // the file path such as: files/userId/attachs/adfadf.doc + CreatedTime time.Time `CreatedTime` + + // FromFileId bson.ObjectId `bson:"FromFileId,omitempty"` // copy from fileId, for collaboration +} diff --git a/app/info/FileInfo.go b/app/info/FileInfo.go index 199d53c..66a7995 100644 --- a/app/info/FileInfo.go +++ b/app/info/FileInfo.go @@ -11,9 +11,11 @@ type File struct { AlbumId bson.ObjectId `bson:"AlbumId"` Name string `Name` // file name Title string `Title` // file name or user defined for search - Size int64 `Size` // file size (byte) - Type string `Type` // file type, such as image/jpg - Path string `Path` // the file path, based on /upload + Size int64 `Size` // file size (byte) + Type string `Type` // file type, "" = image, "doc" = word + Path string `Path` // the file path IsDefaultAlbum bool `IsDefaultAlbum` CreatedTime time.Time `CreatedTime` + + FromFileId bson.ObjectId `bson:"FromFileId,omitempty"` // copy from fileId, for collaboration } diff --git a/app/info/NoteImage.go b/app/info/NoteImage.go new file mode 100644 index 0000000..38643cf --- /dev/null +++ b/app/info/NoteImage.go @@ -0,0 +1,12 @@ +package info + +import ( + "gopkg.in/mgo.v2/bson" +) + +// 笔记内部图片 +type NoteImage struct { + NoteImageId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键 + NoteId bson.ObjectId `bson:"NoteId"` // 笔记 + ImageId bson.ObjectId `bson:"ImageId"` // 图片fileId +} \ No newline at end of file diff --git a/app/info/NoteInfo.go b/app/info/NoteInfo.go index 0b3b513..9ba45fd 100644 --- a/app/info/NoteInfo.go +++ b/app/info/NoteInfo.go @@ -24,6 +24,8 @@ type Note struct { IsMarkdown bool `IsMarkdown` // 是否是markdown笔记, 默认是false + AttachNum int `AttachNum` // 2014/9/21, attachments num + CreatedTime time.Time `CreatedTime` UpdatedTime time.Time `UpdatedTime` UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了 @@ -54,11 +56,11 @@ type NoteAndContent struct { // 每一个历史记录对象 type EachHistory struct { UpdatedUserId bson.ObjectId `UpdatedUserId` - UpdatedTime time.Time `UpdatedTime` - Content string `Content` + UpdatedTime time.Time `UpdatedTime` + Content string `Content` } type NoteContentHistory struct { - NoteId bson.ObjectId `bson:"_id,omitempty"` - UserId bson.ObjectId `bson:"UserId"` // 所属者 - Histories []EachHistory `Histories` -} \ No newline at end of file + NoteId bson.ObjectId `bson:"_id,omitempty"` + UserId bson.ObjectId `bson:"UserId"` // 所属者 + Histories []EachHistory `Histories` +} diff --git a/app/lea/File.go b/app/lea/File.go index a1f4dff..503f165 100644 --- a/app/lea/File.go +++ b/app/lea/File.go @@ -4,6 +4,7 @@ import ( "strings" "path/filepath" "os" + "io" ) // 分离文件名与扩展名(包含.) @@ -62,4 +63,18 @@ func ListDir(dir string) []string { } names, _ := f.Readdirnames(0) return names +} + +func CopyFile(srcName, dstName string) (written int64, err error) { + src, err := os.Open(srcName) + if err != nil { + return + } + defer src.Close() + dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return + } + defer dst.Close() + return io.Copy(dst, src) } \ No newline at end of file diff --git a/app/lea/Util.go b/app/lea/Util.go index 1685802..3d28047 100644 --- a/app/lea/Util.go +++ b/app/lea/Util.go @@ -266,4 +266,16 @@ func RandomPwd(num int) string { } return str +} + +func InArray(arr []string, str string) bool { + if arr == nil { + return false + } + for _, v := range arr { + if v == str { + return true + } + } + return false } \ No newline at end of file diff --git a/app/service/AttachService.go b/app/service/AttachService.go new file mode 100644 index 0000000..521558d --- /dev/null +++ b/app/service/AttachService.go @@ -0,0 +1,175 @@ +package service + +import ( + . "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" +) + +type AttachService struct { +} + +// add attach +func (this *AttachService) AddAttach(attach info.Attach) bool { + attach.CreatedTime = time.Now() + ok := db.Insert(db.Attachs, attach) + + if ok { + // 更新笔记的attachs num + this.updateNoteAttachNum(attach.NoteId, 1) + } + + return ok +} + +// 更新笔记的附件个数 +// addNum 1或-1 +func (this *AttachService) updateNoteAttachNum(noteId bson.ObjectId, addNum int) bool { + num := db.Count(db.Attachs, bson.M{"NoteId": noteId}) + /* + note := info.Note{} + note = noteService.GetNoteById(noteId.Hex()) + note.AttachNum += addNum + if note.AttachNum < 0 { + note.AttachNum = 0 + } + Log(note.AttachNum) + */ + return db.UpdateByQField(db.Notes, bson.M{"_id": noteId}, "AttachNum", num) +} + +// list attachs +func (this *AttachService) ListAttachs(noteId, userId string) []info.Attach { + attachs := []info.Attach{} + // 判断是否有权限为笔记添加附件 + if !shareService.HasUpdateNotePerm(noteId, userId) { + return attachs + } + + db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs) + + return attachs +} + +func (this *AttachService) UpdateImageTitle(userId, fileId, title string) bool { + return db.UpdateByIdAndUserIdField(db.Files, fileId, userId, "Title", title) +} + + +// Delete note to delete attas firstly +func (this *AttachService) DeleteAllAttachs(noteId, userId string) bool { + note := noteService.GetNoteById(noteId) + if note.UserId.Hex() == userId { + attachs := []info.Attach{} + db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs) + for _, attach := range attachs { + attach.Path = strings.TrimLeft(attach.Path, "/") + os.Remove(revel.BasePath + "/" + attach.Path) + } + return true + } + + return false +} + +// delete attach +func (this *AttachService) DeleteAttach(attachId, userId string) (bool, string) { + attach := info.Attach{} + db.Get(db.Attachs, attachId, &attach) + + if(attach.AttachId != "") { + // 判断是否有权限为笔记添加附件 + if !shareService.HasUpdateNotePerm(attach.NoteId.Hex(), userId) { + return false, "No Perm" + } + + if db.Delete(db.Attachs, bson.M{"_id": bson.ObjectIdHex(attachId)}) { + this.updateNoteAttachNum(attach.NoteId, -1) + attach.Path = strings.TrimLeft(attach.Path, "/") + err := os.Remove(revel.BasePath + "/" + attach.Path) + if err == nil { + return true, "delete file error" + } + return false, "delete file error" + } + return false, "db error" + } + return false, "no such item" +} + +// 获取文件路径 +// 要判断是否具有权限 +// userId是否具有attach的访问权限 +func (this *AttachService) GetAttach(attachId, userId string) (attach info.Attach) { + if attachId == "" { + return + } + + attach = info.Attach{} + db.Get(db.Attachs, attachId, &attach) + path := attach.Path + if path == "" { + return + } + + note := noteService.GetNoteById(attach.NoteId.Hex()) + + // 判断权限 + + // 笔记是否是公开的 + if note.IsBlog { + return + } + + // 笔记是否是我的 + if note.UserId.Hex() == userId { + return + } + + // 我是否有权限查看或协作 + if shareService.HasReadNotePerm(attach.NoteId.Hex(), userId) { + return + } + + attach = info.Attach{} + return +} + +// 复制笔记时需要复制附件 +// noteService调用, 权限已判断 +func (this *AttachService) CopyAttachs(noteId, toNoteId, toUserId string) bool { + attachs := []info.Attach{} + db.ListByQ(db.Attachs, bson.M{"NoteId": bson.ObjectIdHex(noteId)}, &attachs) + + // 复制之 + toNoteIdO := bson.ObjectIdHex(toNoteId) + for _, attach := range attachs { + attach.AttachId = "" + attach.NoteId = toNoteIdO + + // 文件复制一份 + _, ext := SplitFilename(attach.Name) + newFilename := NewGuid() + ext + dir := "files/" + toUserId + "/attachs" + filePath := dir + "/" + newFilename + err := os.MkdirAll(revel.BasePath + "/" + dir, 0755) + if err != nil { + return false + } + _, err = CopyFile(revel.BasePath + "/" + attach.Path, revel.BasePath + "/" + filePath) + if err != nil { + return false + } + attach.Name = newFilename + attach.Path = filePath + + this.AddAttach(attach) + } + + return true +} \ No newline at end of file diff --git a/app/service/FileService.go b/app/service/FileService.go index 7d8a7bd..8a8cfc3 100644 --- a/app/service/FileService.go +++ b/app/service/FileService.go @@ -1,7 +1,7 @@ package service import ( -// . "github.com/leanote/leanote/app/lea" + . "github.com/leanote/leanote/app/lea" "github.com/revel/revel" "github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/db" @@ -35,7 +35,7 @@ func (this *FileService) ListImagesWithPage(userId, albumId, key string, pageNum skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, "CreatedTime", false) files := []info.File{} - q := bson.M{"UserId": bson.ObjectIdHex(userId)} + q := bson.M{"UserId": bson.ObjectIdHex(userId), "Type": ""} // life if albumId != "" { q["AlbumId"] = bson.ObjectIdHex(albumId); } else { @@ -89,6 +89,7 @@ func (this *FileService) DeleteImage(userId, fileId string) (bool, string) { if(file.FileId != "") { if db.DeleteByIdAndUserId(db.Files, fileId, userId) { // delete image + // TODO err := os.Remove(revel.BasePath + "/public/" + file.Path) if err == nil { return true, "" @@ -103,4 +104,125 @@ func (this *FileService) DeleteImage(userId, fileId string) (bool, string) { // update image title func (this *FileService) UpdateImage(userId, fileId, title string) bool { return db.UpdateByIdAndUserIdField(db.Files, fileId, userId, "Title", title) -} \ No newline at end of file +} + +// 获取文件路径 +// 要判断是否具有权限 +// userId是否具有fileId的访问权限 +func (this *FileService) GetFile(userId, fileId string) string { + if fileId == "" { + return "" + } + + file := info.File{} + db.Get(db.Files, fileId, &file) + path := file.Path + if path == "" { + return "" + } + + // 1. 判断权限 + + // 是否是我的文件 + if userId != "" && file.UserId.Hex() == userId { + return path + } + + // 得到使用过该fileId的所有笔记NoteId + // 这些笔记是否有public的, 若有则ok + // 这些笔记(笔记本)是否有共享给我的, 若有则ok + + noteIds := noteImageService.GetNoteIds(fileId) + if noteIds != nil && len(noteIds) > 0 { + // 这些笔记是否有public的 + if db.Has(db.Notes, bson.M{"_id": bson.M{"$in": noteIds}, "IsBlog": true}) { + return path + } + + // 若有共享给我的笔记? + if db.Has(db.ShareNotes, bson.M{"ToUserId": bson.ObjectIdHex(userId), "NoteId": bson.M{"$in": noteIds}}) { + return path + } + + // 笔记本是否共享给我? + // 通过笔记得到笔记本 + notes := []info.Note{} + db.ListByQWithFields(db.Notes, bson.M{"_id": bson.M{"$in": noteIds}}, []string{"NotebookId"}, ¬es) + if notes != nil && len(notes) > 0 { + notebookIds := make([]bson.ObjectId, len(notes)) + for i := 0; i < len(notes); i++ { + notebookIds[i] = notes[i].NotebookId + } + + if db.Has(db.ShareNotebooks, bson.M{"ToUserId": bson.ObjectIdHex(userId), "NotebookId": bson.M{"$in": notebookIds}}) { + return path + } + } + } + + // 可能是刚复制到owner上, 但内容又没有保存, 所以没有note->imageId的映射, 此时看是否有fromFileId + if file.FromFileId != "" { + fromFile := info.File{} + db.Get2(db.Files, file.FromFileId, &fromFile) + if fromFile.UserId.Hex() == userId { + return fromFile.Path + } + } + + return "" +} + +// 复制图片 +func (this *FileService) CopyImage(userId, fileId, toUserId string) (bool, string) { + // 是否已经复制过了 + file2 := info.File{} + db.GetByQ(db.Files, bson.M{"UserId": bson.ObjectIdHex(toUserId), "FromFileId": bson.ObjectIdHex(fileId)}, &file2) + if file2.FileId != "" { + return true, file2.FileId.Hex(); + } + + // 复制之 + + file := info.File{} + db.GetByIdAndUserId(db.Files, fileId, userId, &file) + + if file.FileId == "" || file.UserId.Hex() != userId { + return false, "" + } + + _, ext := SplitFilename(file.Name) + newFilename := NewGuid() + ext + + dir := "files/" + toUserId + "/images" + filePath := dir + "/" + newFilename + err := os.MkdirAll(dir, 0755) + if err != nil { + return false, "" + } + + _, err = CopyFile(revel.BasePath + "/" + file.Path, revel.BasePath + "/" + filePath) + if err != nil { + Log(err) + return false, "" + } + + fileInfo := info.File{Name: newFilename, + Title: file.Title, + Path: filePath, + Size: file.Size, + FromFileId: file.FileId} + id := bson.NewObjectId(); + fileInfo.FileId = id + fileId = id.Hex() + Ok := this.AddImage(fileInfo, "", toUserId) + + if Ok { + return Ok, id.Hex() + } + return false, "" +} + +// 是否是我的文件 +func (this *FileService) IsMyFile(userId, fileId string) bool { + 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 new file mode 100644 index 0000000..11dcf6d --- /dev/null +++ b/app/service/NoteImageService.go @@ -0,0 +1,102 @@ +package service + +import ( + "github.com/leanote/leanote/app/info" + "github.com/leanote/leanote/app/db" + . "github.com/leanote/leanote/app/lea" + "gopkg.in/mgo.v2/bson" + "regexp" +// "time" +) + +type NoteImageService struct { +} + +// 通过id, userId得到noteIds +func (this *NoteImageService) GetNoteIds(imageId string) ([]bson.ObjectId) { + noteImages := []info.NoteImage{} + db.ListByQWithFields(db.NoteImages, bson.M{"ImageId": bson.ObjectIdHex(imageId)}, []string{"NoteId"}, ¬eImages) + + if noteImages != nil && len(noteImages) > 0 { + noteIds := make([]bson.ObjectId, len(noteImages)) + cnt := len(noteImages) + for i := 0; i < cnt; i++ { + noteIds[i] = noteImages[i].NoteId + } + return noteIds + } + + return nil +} + +// 解析内容中的图片, 建立图片与note的关系 +// +// 图片必须是我的, 不然不添加 +func (this *NoteImageService) UpdateNoteImages(userId, noteId, content string) bool { + reg, _ := regexp.Compile("outputImage\\?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 _, ok := hasAdded[fileId]; !ok { + Log(fileId) + // 判断是否是我的文件 + if fileService.IsMyFile(userId, fileId) { + noteImage.ImageId = bson.ObjectIdHex(fileId) + db.Insert(db.NoteImages, noteImage) + } + hasAdded[fileId] = true + } + } + } + } + + return true +} + +// 复制图片, 把note的图片都copy给我, 且修改noteContent图片路径 +func (this *NoteImageService) CopyNoteImages(fromNoteId, fromUserId, newNoteId, content, toUserId string) string { + // 得到fromNoteId的noteImages, 如果为空, 则直接返回content + noteImages := []info.NoteImage{} + db.ListByQWithFields(db.NoteImages, bson.M{"NoteId": bson.ObjectIdHex(fromNoteId)}, []string{"ImageId"}, ¬eImages) + + if len(noteImages) == 0 { + return content; + } + + // + // 把fileId=1232替换成新的 + replaceMap := map[string]string{} + for _, noteImage := range noteImages { + imageId := noteImage.ImageId.Hex() + ok, newImageId := fileService.CopyImage(fromUserId, imageId, toUserId) + if ok { + replaceMap[imageId] = newImageId + } + } + + if len(replaceMap) > 0 { + // 替换之 + reg, _ := regexp.Compile("outputImage\\?fileId=([a-z0-9A-Z]{24})") + content = reg.ReplaceAllStringFunc(content, func(each string) string { + // each=outputImage?fileId=541bd2f599c37b4f3r000003 + fileId := each[len(each)-24:] // 得到后24位, 也即id + if replaceFileId, ok := replaceMap[fileId]; ok { + return "outputImage?fileId=" + replaceFileId + } + return each + }); + } + + return content; +} diff --git a/app/service/NoteService.go b/app/service/NoteService.go index f1341fd..7fc2504 100644 --- a/app/service/NoteService.go +++ b/app/service/NoteService.go @@ -17,6 +17,12 @@ func (this *NoteService) GetNote(noteId, userId string) (note info.Note) { db.GetByIdAndUserId(db.Notes, noteId, userId, ¬e) return } +// fileService调用 +func (this *NoteService) GetNoteById(noteId string) (note info.Note) { + note = info.Note{} + db.Get(db.Notes, noteId, ¬e) + return +} // 得到blog, blogService用 // 不要传userId, 因为是公开的 func (this *NoteService) GetBlogNote(noteId string) (note info.Note) { @@ -148,6 +154,10 @@ func (this *NoteService) AddNoteContent(noteContent info.NoteContent) info.NoteC noteContent.UpdatedTime = noteContent.CreatedTime noteContent.UpdatedUserId = noteContent.UserId db.Insert(db.NoteContents, noteContent) + + // 更新笔记图片 + noteImageService.UpdateNoteImages(noteContent.UserId.Hex(), noteContent.NoteId.Hex(), noteContent.Content) + return noteContent; } @@ -237,11 +247,23 @@ func (this *NoteService) UpdateNoteContent(userId, updatedUserId, noteId, conten Content: content, UpdatedTime: time.Now(), }) + + // 更新笔记图片 + noteImageService.UpdateNoteImages(userId, noteId, content) + return true } return false } +// ????? +// 这种方式太恶心, 改动很大 +// 通过content修改笔记的imageIds列表 +// src="http://localhost:9000/file/outputImage?fileId=541ae75499c37b6b79000005¬eId=541ae63c19807a4bb9000000" +func (this *NoteService) updateNoteImages(noteId string, content string) bool { + return true +} + // 更新tags // [ok] [del] func (this *NoteService) UpdateTags(noteId string, userId string, tags []string) bool { @@ -316,9 +338,12 @@ func (this *NoteService) CopyNote(noteId, notebookId, userId string) info.Note { } // 复制别人的共享笔记给我 -// TODO 判断是否共享了给我 +// 将别人可用的图片转为我的图片, 复制图片 func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId string) info.Note { - if notebookService.IsMyNotebook(notebookId, myUserId) { + Log(shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId)) + // 判断是否共享了给我 + if notebookService.IsMyNotebook(notebookId, myUserId) && + (shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId)) { note := this.GetNote(noteId, fromUserId) if note.NoteId == "" { return info.Note{} @@ -332,10 +357,18 @@ func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId note.IsTop = false note.IsBlog = false // 别人的可能是blog + note.ImgSrc = "" // 为什么清空, 因为图片需要复制, 先清空 + // content noteContent.NoteId = note.NoteId noteContent.UserId = note.UserId + // 复制图片, 把note的图片都copy给我, 且修改noteContent图片路径 + noteContent.Content = noteImageService.CopyNoteImages(noteId, fromUserId, note.NoteId.Hex(), noteContent.Content, myUserId) + + // 复制附件 + attachService.CopyAttachs(noteId, note.NoteId.Hex(), myUserId) + // 添加之 note = this.AddNoteAndContent(note, noteContent, note.UserId); @@ -353,8 +386,10 @@ func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId // shareService call // [ok] func (this *NoteService) GetNotebookId(noteId string) bson.ObjectId { - note := &info.Note{} - db.Get(db.Notes, noteId, note) + note := info.Note{} + // db.Get(db.Notes, noteId, ¬e) + // LogJ(note) + db.GetByQWithFields(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, []string{"NotebookId"}, ¬e) return note.NotebookId } @@ -396,7 +431,6 @@ func (this *NoteService) searchNoteFromContent(notes []info.Note, userId, key st for i, note := range notes { noteIds[i] = note.NoteId } - LogJ(noteIds) noteContents := []info.NoteContent{} query := bson.M{"_id": bson.M{"$nin": noteIds}, "UserId": bson.ObjectIdHex(userId), "Content": bson.M{"$regex": bson.RegEx{".*?" + key + ".*", "i"}}} if isBlog { @@ -419,9 +453,6 @@ func (this *NoteService) searchNoteFromContent(notes []info.Note, userId, key st noteIds2[i] = content.NoteId } -// Log(" content search ") -// Log(lenContent) - // 得到notes notes2 := this.ListNotesByNoteIds(noteIds2) @@ -446,8 +477,6 @@ func (this *NoteService) SearchNoteByTags(tags []string, userId string, pageNumb // 总记录数 count, _ = q.Count() - Log(count) - q.Sort(sortFieldR). Skip(skipNum). Limit(pageSize). diff --git a/app/service/ShareService.go b/app/service/ShareService.go index 8e80e55..ebad5b0 100644 --- a/app/service/ShareService.go +++ b/app/service/ShareService.go @@ -3,7 +3,7 @@ package service import ( "github.com/leanote/leanote/app/info" "github.com/leanote/leanote/app/db" -// . "github.com/leanote/leanote/app/lea" + . "github.com/leanote/leanote/app/lea" "gopkg.in/mgo.v2/bson" "time" "sort" @@ -286,6 +286,28 @@ func (this *ShareService) AddShareNote(noteId string, perm int, userId, email st return db.Insert(db.ShareNotes, shareNote), "", toUserId } +// updatedUserId是否有查看userId noteId的权限? +func (this *ShareService) HasReadPerm(userId, updatedUserId, noteId string) bool { + if !db.Has(db.ShareNotes, + bson.M{"UserId": bson.ObjectIdHex(userId), "ToUserId": bson.ObjectIdHex(updatedUserId), "NoteId": bson.ObjectIdHex(noteId)}) { + // noteId的notebookId是否被共享了? + notebookId := noteService.GetNotebookId(noteId) + if notebookId.Hex() == "" { + return false + } + + // 判断notebook是否被共享 + if !db.Has(db.ShareNotebooks, + bson.M{"UserId": bson.ObjectIdHex(userId), "ToUserId": bson.ObjectIdHex(updatedUserId), "NotebookId": notebookId}) { + return false + } else { + return true + } + } else { + return true + } +} + // updatedUserId是否有修改userId noteId的权限? func (this *ShareService) HasUpdatePerm(userId, updatedUserId, noteId string) bool { // 1. noteId是否被共享了? @@ -334,14 +356,14 @@ func (this *ShareService) AddHasShareNote(userId, toUserId string) bool { } // userId是否被共享了noteId -func (this *ShareService) hasSharedNote(noteId, myUserId string) bool { +func (this *ShareService) HasSharedNote(noteId, myUserId string) bool { return db.Has(db.ShareNotes, bson.M{"ToUserId": bson.ObjectIdHex(myUserId), "NoteId": bson.ObjectIdHex(noteId)}) } // noteId的notebook是否共享了给我 -func (this *ShareService) hasSharedNotebook(noteId, myUserId, sharedUserId string) bool { - note := noteService.GetNote(noteId, sharedUserId) - if note.NoteId != "" { - return db.Has(db.ShareNotebooks, bson.M{"NotebookId": note.NotebookId, +func (this *ShareService) HasSharedNotebook(noteId, myUserId, sharedUserId string) bool { + notebookId := noteService.GetNotebookId(noteId) + if notebookId != "" { + return db.Has(db.ShareNotebooks, bson.M{"NotebookId": notebookId, "UserId": bson.ObjectIdHex(sharedUserId), "ToUserId": bson.ObjectIdHex(myUserId), }) @@ -355,7 +377,7 @@ func (this *ShareService) GetShareNoteContent(noteId, myUserId, sharedUserId str noteContent = info.NoteContent{} // 是否单独共享了该notebook // 或者, 其notebook共享了我 - if this.hasSharedNote(noteId, myUserId) || this.hasSharedNotebook(noteId, myUserId, sharedUserId) { + if this.HasSharedNote(noteId, myUserId) || this.HasSharedNotebook(noteId, myUserId, sharedUserId) { db.Get(db.NoteContents, noteId, ¬eContent) } else { } @@ -507,4 +529,51 @@ func (this *ShareService) DeleteUserShareNoteAndNotebook(userId, toUserId string db.DeleteAll(db.HasShareNotes, query); return true +} + +// 用户userId是否有修改noteId的权限 +func (this *ShareService) HasUpdateNotePerm(noteId, userId string) bool { + if noteId == "" || userId == "" { + return false; + } + note := noteService.GetNoteById(noteId) + LogJ(note); + if note.UserId != "" { + noteUserId := note.UserId.Hex() + if noteUserId != userId { + // 是否是有权限协作的 + if this.HasUpdatePerm(noteUserId, userId, noteId) { + return true + } else { + return false; + } + } else { + return true + } + } else { + return false; + } +} + +// 用户userId是否有修改noteId的权限 +func (this *ShareService) HasReadNotePerm(noteId, userId string) bool { + if noteId == "" || userId == "" { + return false; + } + note := noteService.GetNoteById(noteId) + if note.UserId != "" { + noteUserId := note.UserId.Hex() + if noteUserId != userId { + // 是否是有权限协作的 + if this.HasReadPerm(noteUserId, userId, noteId) { + return true + } else { + return false; + } + } else { + return true + } + } else { + return false; + } } \ No newline at end of file diff --git a/app/service/TrashService.go b/app/service/TrashService.go index 23b972a..014e6ac 100644 --- a/app/service/TrashService.go +++ b/app/service/TrashService.go @@ -53,7 +53,15 @@ func (this *TrashService) recoverNote(noteId, notebookId, userId string) bool { // 删除trash func (this *TrashService) DeleteTrash(noteId, userId string) bool { - return db.DeleteByIdAndUserId(db.Notes, noteId, userId) + // delete note's attachs + ok := attachService.DeleteAllAttachs(noteId, userId) + + // delete note + ok = db.DeleteByIdAndUserId(db.Notes, noteId, userId) + // delete content + ok = db.DeleteByIdAndUserId(db.NoteContents, noteId, userId) + + return ok } // 列出note, 排序规则, 还有分页 diff --git a/app/service/init.go b/app/service/init.go index cd020c4..d8bf943 100644 --- a/app/service/init.go +++ b/app/service/init.go @@ -17,6 +17,9 @@ var userService *UserService var tagService *TagService var blogService *BlogService var tokenService *TokenService +var noteImageService *NoteImageService +var fileService *FileService +var attachService *AttachService func init() { notebookService = &NotebookService{} @@ -28,4 +31,7 @@ func init() { tagService = &TagService{} blogService = &BlogService{} tokenService = &TokenService{} + fileService = &FileService{} + attachService = &AttachService{} + noteImageService = &NoteImageService{} } \ No newline at end of file diff --git a/app/views/Note/note-dev.html b/app/views/Note/note-dev.html index d65a11c..088c817 100644 --- a/app/views/Note/note-dev.html +++ b/app/views/Note/note-dev.html @@ -353,6 +353,7 @@ function log(o) { Sort --> + @@ -466,6 +468,38 @@ function log(o) {