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 3d172b6..91cdb6b 100644 --- a/app/controllers/FileController.go +++ b/app/controllers/FileController.go @@ -17,7 +17,6 @@ type File struct { BaseController } -// 上传图片 editor // 过时 已弃用! func (c File) UploadImage(renderHtml string) revel.Result { if renderHtml == "" { @@ -33,18 +32,19 @@ 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"); } -// 弃用 -func (c File) UploadImageJson(from, noteId string) revel.Result { - re := c.uploadImage(from, ""); - return c.RenderJson(re) -} - // 拖拉上传, pasteImage // noteId 是为了判断是否是协作的note, 如果是则需要复制一份到note owner中 func (c File) PasteImage(noteId string) revel.Result { @@ -69,13 +69,14 @@ func (c File) PasteImage(noteId string) revel.Result { return c.RenderJson(re) } -// leaui image plugin +// leaui image plugin upload image func (c File) UploadImageLeaui(albumId string) revel.Result { re := c.uploadImage("", albumId); return c.RenderJson(re) } // 上传图片, 公用方法 +// upload image common func func (c File) uploadImage(from, albumId string) (re info.Re) { var fileUrlPath = "" var fileId = "" diff --git a/app/controllers/init.go b/app/controllers/init.go index 9e99661..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" diff --git a/app/db/Mgo.go b/app/db/Mgo.go index e68ca3a..6f91f8b 100644 --- a/app/db/Mgo.go +++ b/app/db/Mgo.go @@ -36,6 +36,7 @@ var Suggestions *mgo.Collection // Album & file(image) var Albums *mgo.Collection var Files *mgo.Collection +var Attachs *mgo.Collection var NoteImages *mgo.Collection @@ -106,6 +107,7 @@ 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") } 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/NoteInfo.go b/app/info/NoteInfo.go index 103e2df..9ba45fd 100644 --- a/app/info/NoteInfo.go +++ b/app/info/NoteInfo.go @@ -24,7 +24,7 @@ type Note struct { IsMarkdown bool `IsMarkdown` // 是否是markdown笔记, 默认是false - AttachIds []string `FileIds,omitempty` // 2014/9/18, attachments + AttachNum int `AttachNum` // 2014/9/21, attachments num CreatedTime time.Time `CreatedTime` UpdatedTime time.Time `UpdatedTime` 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 37bd4f9..8a8cfc3 100644 --- a/app/service/FileService.go +++ b/app/service/FileService.go @@ -189,7 +189,6 @@ func (this *FileService) CopyImage(userId, fileId, toUserId string) (bool, strin if file.FileId == "" || file.UserId.Hex() != userId { return false, "" } - Log(file) _, ext := SplitFilename(file.Name) newFilename := NewGuid() + ext diff --git a/app/service/NoteService.go b/app/service/NoteService.go index 8f25aad..7fc2504 100644 --- a/app/service/NoteService.go +++ b/app/service/NoteService.go @@ -366,6 +366,9 @@ func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId // 复制图片, 把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); diff --git a/app/service/ShareService.go b/app/service/ShareService.go index 1e13c35..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是否被共享了? @@ -340,8 +362,6 @@ func (this *ShareService) HasSharedNote(noteId, myUserId string) bool { // noteId的notebook是否共享了给我 func (this *ShareService) HasSharedNotebook(noteId, myUserId, sharedUserId string) bool { notebookId := noteService.GetNotebookId(noteId) - Log(noteId) - Log(notebookId) if notebookId != "" { return db.Has(db.ShareNotebooks, bson.M{"NotebookId": notebookId, "UserId": bson.ObjectIdHex(sharedUserId), @@ -509,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 985e938..d8bf943 100644 --- a/app/service/init.go +++ b/app/service/init.go @@ -19,6 +19,7 @@ var blogService *BlogService var tokenService *TokenService var noteImageService *NoteImageService var fileService *FileService +var attachService *AttachService func init() { notebookService = &NotebookService{} @@ -31,5 +32,6 @@ func init() { 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 520221f..171a4a1 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) {