Просмотр исходного кода

Implement image-upload with new page

Sotaro KARASAWA 10 лет назад
Родитель
Сommit
968b8e11c6

+ 7 - 4
lib/models/attachment.js

@@ -13,11 +13,12 @@ module.exports = function(crowi) {
   }
 
   attachmentSchema = new mongoose.Schema({
-    page: { type: ObjectId, ref: 'Page' },
-    creator: { type: ObjectId, ref: 'User' },
+    page: { type: ObjectId, ref: 'Page', index: true },
+    creator: { type: ObjectId, ref: 'User', index: true  },
     filePath: { type: String, required: true },
     fileName: { type: String, required: true },
     fileFormat: { type: String, required: true },
+    fileSize: { type: Number, default: 0 },
     createdAt: { type: Date, default: Date.now }
   });
 
@@ -48,7 +49,7 @@ module.exports = function(crowi) {
     });
   };
 
-  attachmentSchema.statics.create = function(pageId, creator, filePath, fileName, fileFormat) {
+  attachmentSchema.statics.create = function(pageId, creator, filePath, fileName, fileFormat, fileSize) {
     var Attachment = this;
 
     return new Promise(function(resolve, reject) {
@@ -59,13 +60,15 @@ module.exports = function(crowi) {
       newAttachment.filePath = filePath;
       newAttachment.fileName = fileName;
       newAttachment.fileFormat = fileFormat;
+      newAttachment.fileSize = fileSize;
       newAttachment.createdAt = Date.now();
 
       newAttachment.save(function(err, data) {
-        debug('Attachment saved.', data);
         if (err) {
+          debug('Error on saving attachment.', err);
           return reject(err);
         }
+        debug('Attachment saved.', data);
         return resolve(data);
       });
     });

+ 24 - 5
lib/models/page.js

@@ -50,8 +50,18 @@ module.exports = function(crowi) {
     return false;
   };
 
+  pageSchema.methods.isCreator = function(userData) {
+    if (this.populated('creator') && this.creator._id.toString() === userData._id.toString()) {
+      return true;
+    } else if (this.creator.toString() === userData._id.toString()) {
+      return true
+    }
+
+    return false;
+  };
+
   pageSchema.methods.isGrantedFor = function(userData) {
-    if (this.isPublic()) {
+    if (this.isPublic() || this.isCreator(userData)) {
       return true;
     }
 
@@ -346,13 +356,18 @@ module.exports = function(crowi) {
   };
 
   pageSchema.statics.pushRevision = function(pageData, newRevision, user, cb) {
-    pageData.revision = newRevision._id;
-    pageData.updatedAt = Date.now();
-
     newRevision.save(function(err, newRevision) {
+      if (err) {
+        debug('Error on saving revision', err);
+        return cb(err, null);
+      }
+
+      debug('Successfully saved new revision', newRevision);
+      pageData.revision = newRevision._id;
+      pageData.updatedAt = Date.now();
       pageData.save(function(err, data) {
         if (err) {
-          console.log('Error on save page data', err);
+          debug('Error on save page data (after push revision)', err);
           cb(err, null);
           return;
         }
@@ -365,6 +380,7 @@ module.exports = function(crowi) {
     var Page = this
       , Revision = crowi.model('Revision')
       , format = options.format || 'markdown'
+      , grant = options.grant || GRANT_PUBLIC
       , redirectTo = options.redirectTo || null;
 
     this.findOne({path: path}, function(err, pageData) {
@@ -379,6 +395,9 @@ module.exports = function(crowi) {
       newPage.createdAt = Date.now();
       newPage.updatedAt = Date.now();
       newPage.redirectTo = redirectTo;
+      newPage.grant = grant;
+      newPage.grantedUsers = [];
+      newPage.grantedUsers.push(user);
       newPage.save(function (err, newPage) {
 
         var newRevision = Revision.prepareRevision(newPage, body, user, {format: format});

+ 81 - 47
lib/routes/attachment.js

@@ -2,8 +2,10 @@ module.exports = function(crowi, app) {
   'use strict';
 
   var debug = require('debug')('crowi:routs:attachment')
-    ,  Attachment = crowi.model('Attachment')
+    , Attachment = crowi.model('Attachment')
     , User = crowi.model('User')
+    , Page = crowi.model('Page')
+    , Promise = require('bluebird')
     , fs = require('fs')
     , actions = {}
     , api = {};
@@ -28,13 +30,16 @@ module.exports = function(crowi, app) {
    *
    */
   api.add = function(req, res){
-    var id = req.params.pageId;
-    if (id == 0) {
-      // TODO create page before process upload
-    }
+    var id = req.params.pageId,
+      path = decodeURIComponent(req.body.path),
+      pageCreated = false,
+      page = {};
+
+    debug('id and path are: ', id, path);
 
     var fileUploader = require('../util/fileUploader')(crowi, app);
     var tmpFile = req.files.file || null;
+    debug('Uploaded tmpFile: ', tmpFile);
     if (!tmpFile) {
       return res.json({
         status: false,
@@ -42,56 +47,85 @@ module.exports = function(crowi, app) {
       });
     }
 
-    var tmpPath = tmpFile.path,
-      fileName = tmpFile.name,
-      fileType = tmpFile.mimetype,
-      filePath = Attachment.createAttachmentFilePath(id, fileName, fileType);
+    new Promise(function(resolve, reject) {
+      if (id == 0) {
+        debug('Create page before file upload');
+        Page.create(path, '# '  + path, req.user, {grant: Page.GRANT_OWNER}, function(err, pageData) {
+          if (err) {
+            debug('Page create error', err);
+            return reject(err);
+          }
+          pageCreated = true;
+          return resolve(pageData);
+        });
+      } else {
+        Page.findPageById(id, function(err, pageData){
+          if (err) {
+            debug('Page find error', err);
+            return reject(err);
+          }
+          return resolve(pageData);
+        });
+      }
+    }).then(function(pageData) {
+      page = pageData;
+      id = pageData._id;
 
-    fileUploader.uploadFile(
-      filePath,
-      fileType,
-      fs.createReadStream(tmpPath, {
-        flags: 'r',
-        encoding: null,
-        fd: null,
-        mode: '0666',
-        autoClose: true
-      }),
-      {})
-    .then(function(data) {
+      var tmpPath = tmpFile.path,
+        fileName = tmpFile.name,
+        fileType = tmpFile.mimetype,
+        fileSize = tmpFile.size,
+        filePath = Attachment.createAttachmentFilePath(id, fileName, fileType);
 
-      Attachment.create(id, req.user, filePath, fileName, fileType)
-      .then(function(data) {
-        debug('Succesfully save attachment data', data);
+      fileUploader.uploadFile(
+        filePath,
+        fileType,
+        fs.createReadStream(tmpPath, {
+          flags: 'r',
+          encoding: null,
+          fd: null,
+          mode: '0666',
+          autoClose: true
+        }),
+        {}
+      ).then(function(data) {
+        debug('Uploaded data is: ', data);
 
-        var imageUrl = fileUploader.generateS3FileUrl(data.filePath);
-        return res.json({
-          status: true,
-          filename: imageUrl,
-          attachment: data,
-          message: 'Successfully uploaded.',
+        // TODO size
+        Attachment.create(id, req.user, filePath, fileName, fileType, fileSize)
+        .then(function(data) {
+          var imageUrl = fileUploader.generateS3FileUrl(data.filePath);
+          return res.json({
+            status: true,
+            filename: imageUrl,
+            attachment: data,
+            page: page,
+            pageCreated: pageCreated,
+            message: 'Successfully uploaded.',
+          });
+        }, function (err) {
+          debug('Error on saving attachment data', err);
+
+          // @TODO
+          // Remove from S3
+          return res.json({
+            status: false,
+            message: '',
+          });
+        }).finally(function() {
+          fs.unlink(tmpPath, function (err) {
+            if (err) {
+              debug('Error while deleting tmp file.');
+            }
+          });
         });
-      }, function (err) {
-        debug('Error on saving attachment data', err);
+      }, function(err) {
+        debug('Upload error to S3.', err);
 
         return res.json({
           status: false,
-          message: '',
+          message: 'Error while uploading.',
         });
-      }).finally(function() {
-        fs.unlink(tmpPath, function (err) {
-          if (err) {
-            debug('Error while deleting tmp file.');
-          }
-        });
-      });
-
-    }, function(err) {
-      debug('Upload error to S3.', err);
-
-      return res.json({
-        status: false,
-        message: 'Error while uploading.',
       });
     });
   };

+ 2 - 1
lib/routes/page.js

@@ -107,6 +107,7 @@ module.exports = function(crowi, app) {
         res.redirect(encodeURI(path));
         return ;
       }
+      debug('Page found', pageData);
 
       if (err == Page.PAGE_GRANT_ERROR) {
         debug('PAGE_GRANT_ERROR');
@@ -168,7 +169,7 @@ module.exports = function(crowi, app) {
         var newRevision = Revision.prepareRevision(pageData, body, req.user);
         Page.pushRevision(pageData, newRevision, req.user, cb);
       } else {
-        Page.create(path, body, req.user, {format: format}, cb);
+        Page.create(path, body, req.user, {format: format, grant: grant}, cb);
       }
     });
   };

+ 73 - 13
lib/views/_form.html

@@ -9,7 +9,7 @@
 </div>
 {% endif %}
 <div id="form-box" class="row">
-  <form action="{{ path }}/edit" method="post" class="col-md-6">
+  <form action="{{ path }}/edit" id="page-form" method="post" class="col-md-6">
     <textarea name="pageForm[body]" class="form-control form-body-height" id="form-body">{% if pageForm.body %}{{ pageForm.body }}{% elseif not revision.body %}# {{ path|path2name }}{% else %}{{ revision.body }}{% endif %}</textarea>
 
     <input type="hidden" name="pageForm[format]" value="markdown" id="form-format">
@@ -74,20 +74,80 @@
       }
     });
 
-    var pageId = $('#content-main').data('page-id') || 0;
-    console.log("pageId", pageId);
-    var attachmentOption = {
-      uploadUrl: '/_api/attachment/page/' + pageId,
-      extraParams: {},
-      progressText: 'Uploading file...',
-      urlText: "\n![file]({filename})\n",
-      onFileUploadResponse: function(res) {
-        console.log("onUploadedFile", res);
+    var unbindInlineAttachment = function($form) {
+      console.log('unbind inlineattach.');
+      $form.unbind('.inlineattach');
+    };
+    var bindInlineAttachment = function($form, attachmentOption) {
+      console.log('bind inline with options:', attachmentOption);
+      var $this = $form;
+      var editor = createEditorInstance($form);
+      var inlineattach = new inlineAttachment(attachmentOption, editor);
+      $form.bind({
+        'paste.inlineattach': function(e) {
+          inlineattach.onPaste(e.originalEvent);
+        },
+        'drop.inlineattach': function(e) {
+          e.stopPropagation();
+          e.preventDefault();
+          inlineattach.onDrop(e.originalEvent);
+        },
+        'dragenter.inlineattach dragover.inlineattach': function(e) {
+          e.stopPropagation();
+          e.preventDefault();
+        }
+      });
+    };
+    var createEditorInstance = function($form) {
+      var $this = $form;
 
-        return true;
-      },
+      return {
+        getValue: function() {
+          return $this.val();
+        },
+        insertValue: function(val) {
+          inlineAttachment.util.insertTextAtCursor($this[0], val);
+        },
+        setValue: function(val) {
+          $this.val(val);
+        }
+      };
     };
-    $('textarea#form-body').inlineattachment(attachmentOption);
+
+    var $inputForm = $('textarea#form-body');
+    if ($inputForm.length > 0) {
+      var pageId = $('#content-main').data('page-id') || 0;
+      var attachmentOption = {
+        uploadUrl: '/_api/attachment/page/' + pageId,
+        extraParams: {
+          path: location.pathname
+        },
+        progressText: '(Uploading file...)',
+        urlText: "\n![file]({filename})\n"
+      };
+
+      attachmentOption.onFileUploadResponse = function(res) {
+        var result = JSON.parse(res.response);
+        console.log("onFileUploadResponse", result);
+
+        if (result.status && result.pageCreated) {
+          var page = result.page,
+            pageId = page._id;
+
+          $('#content-main').data('page-id', page._id);
+          $('#page-form [name="pageForm[currentRevision]"]').val(page.revision)
+
+          unbindInlineAttachment($inputForm);
+
+          attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
+          bindInlineAttachment($inputForm, attachmentOption);
+        }
+        return true;
+      };
+
+      bindInlineAttachment($inputForm, attachmentOption);
+    }
+
     $('textarea#form-body').on('dragenter dragover', function() {
         $(this).addClass('dragover');
       });

+ 2 - 1
resource/css/_layout.scss

@@ -436,7 +436,8 @@
             box-shadow: none;
 
             &.dragover {
-              border: dashed 10px #ccc;
+              border: dashed 6px #ccc;
+              padding: 12px 6px 0px;
             }
           }
           .preview-body {

+ 39 - 16
test/bootstrap.js

@@ -5,6 +5,7 @@ var express = require('express')
   , mongoose= require('mongoose')
   , ROOT_DIR = __dirname + '/..'
   , MODEL_DIR = __dirname + '/../lib/models'
+  , Promise = require('bluebird')
   , mongoUri
   , testDBUtil
   ;
@@ -13,27 +14,49 @@ mongoUri = process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || process.env.MO
 
 
 testDBUtil = {
-  generateFixture: function (conn, model, fixture, cb) {
+  generateFixture: function (conn, model, fixture) {
     var m = conn.model(model);
-    async.each(fixture, function(data, next) {
-      var newDoc = new m;
 
-      Object.keys(data).forEach(function(k) {
-        newDoc[k] = data[k];
-      });
-      newDoc.save(next);
+    return new Promise(function(resolve, reject) {
+      var createdModels = [];
+      fixture.reduce(function(promise, entity) {
+        return promise.then(function() {
+          var newDoc = new m;
+
+          Object.keys(entity).forEach(function(k) {
+            newDoc[k] = entity[k];
+          });
 
-    }, function(err) {
-      cb();
+          return new Promise(function(r, rj) {
+            newDoc.save(function(err, data) {
+              createdModels.push(data);
+              return r();
+            });
+          });
+        });
+      }, Promise.resolve()).then(function() {
+        resolve(createdModels);
+      });
     });
   },
-  cleanUpDb: function (conn, model, cb) {
-    if (!model) {
-      return cb(null, null);
-    }
-
-    var m = conn.model(model);
-    m.remove({}, cb);
+  cleanUpDb: function (conn, models) {
+    return new Promise(function(resolve, reject) {
+      if (Array.isArray(models)) {
+        models.reduce(function(promise, model) {
+          return promise.then(function() {
+            var m = conn.model(model);
+            return new Promise(function(r, rj) {
+              m.remove({}, r);
+            });
+          });
+        }, Promise.resolve()).then(function() {
+          resolve();
+        });
+      } else {
+        var m = conn.model(models);
+        m.remove({}, resolve);
+      }
+    });
   },
 };
 

+ 9 - 4
test/models/config.test.js

@@ -3,6 +3,7 @@ var chai = require('chai')
   , sinon = require('sinon')
   , sinonChai = require('sinon-chai')
   , proxyquire = require('proxyquire')
+  , Promise = require('bluebird')
   ;
 chai.use(sinonChai);
 
@@ -28,7 +29,10 @@ describe('Config model test', function () {
           {ns: 'plugin', key: 'other:config', value: JSON.stringify('this is data')},
         ];
 
-        testDBUtil.generateFixture(conn, 'Config', fixture, done);
+        testDBUtil.generateFixture(conn, 'Config', fixture)
+        .then(function(configs) {
+          done();
+        });
       });
     }
   });
@@ -38,9 +42,10 @@ describe('Config model test', function () {
 
   after(function (done) {
     if (mongoUri) {
-      return testDBUtil.cleanUpDb(conn, 'Config', function(err, doc) {
-        return conn.close(done);
-      });
+      testDBUtil.cleanUpDb(conn, 'Config')
+        .then(function() {
+          return conn.close(done);
+        });
     }
   });
 

+ 104 - 47
test/models/page.test.js

@@ -3,6 +3,7 @@ var chai = require('chai')
   , sinon = require('sinon')
   , sinonChai = require('sinon-chai')
   , proxyquire = require('proxyquire')
+  , Promise = require('bluebird')
   ;
 chai.use(sinonChai);
 
@@ -23,45 +24,79 @@ describe('Page', function () {
         done();
       }
 
-      Page = conn.model('Page');
-      User = conn.model('User');
-
-      var fixture = [
-        {
-          path: '/user/anonymous/memo',
-          grant: Page.GRANT_RESTRICTED,
-          grantedUsers: []
-        },
-        {
-          path: '/grant/public',
-          grant: Page.GRANT_PUBLIC
-        },
-        {
-          path: '/grant/restricted',
-          grant: Page.GRANT_RESTRICTED
-        },
-        {
-          path: '/grant/specified',
-          grant: Page.GRANT_SPECIFIED
-        },
-        {
-          path: '/grant/owner',
-          grant: Page.GRANT_OWNER
-        }
-      ];
-      var userFixture = [
-        {userId: 'anonymous', email: 'anonymous@gmail.com'}
-      ];
-
-      testDBUtil.generateFixture(conn, 'Page', fixture, function() {});
-      testDBUtil.generateFixture(conn, 'User', userFixture, done);
+      var Page = conn.model('Page'),
+        User = conn.model('User');
+
+      new Promise(function(resolve, reject) {
+        testDBUtil.cleanUpDb(conn, 'Page')
+          .then(function() {
+            return testDBUtil.cleanUpDb(conn, 'User')
+          })
+          .then(resolve);
+      }).then(function() {
+        var userFixture = [
+          {name: 'Anon 0', username: 'anonymous0', email: 'anonymous0@example.com'},
+          {name: 'Anon 1', username: 'anonymous1', email: 'anonymous1@example.com'}
+        ];
+
+        return new Promise(function(resolve, reject) {
+          testDBUtil.generateFixture(conn, 'User', userFixture)
+          .then(function(users) {
+            resolve(users);
+          });
+        });
+      }).then(function(testUsers) {
+        var testUser0 = testUsers[0];
+
+        var fixture = [
+          {
+            path: '/user/anonymous/memo',
+            grant: Page.GRANT_RESTRICTED,
+            grantedUsers: [testUser0],
+            creator: testUser0
+          },
+          {
+            path: '/grant/public',
+            grant: Page.GRANT_PUBLIC,
+            grantedUsers: [testUser0],
+            creator: testUser0
+          },
+          {
+            path: '/grant/restricted',
+            grant: Page.GRANT_RESTRICTED,
+            grantedUsers: [testUser0],
+            creator: testUser0
+          },
+          {
+            path: '/grant/specified',
+            grant: Page.GRANT_SPECIFIED,
+            grantedUsers: [testUser0],
+            creator: testUser0
+          },
+          {
+            path: '/grant/owner',
+            grant: Page.GRANT_OWNER,
+            grantedUsers: [testUser0],
+            creator: testUser0
+          }
+        ];
+
+        testDBUtil.generateFixture(conn, 'Page', fixture)
+        .then(function(pages) {
+          done();
+        });
+      });
+
+
     });
   });
 
   after(function (done) {
-    return testDBUtil.cleanUpDb(conn, 'Page', function(err, doc) {
-      return conn.close(done);
-    });
+    testDBUtil.cleanUpDb(conn, 'Page')
+      .then(function() {
+        return testDBUtil.cleanUpDb(conn, 'User')
+      })
+      .then(done);
   });
 
   describe('.isPublic', function () {
@@ -88,23 +123,45 @@ describe('Page', function () {
     });
   });
 
+  describe('.isCreator', function() {
+    context('with creator', function() {
+      it('should return true', function(done) {
+        User.findOne({email: 'anonymous0@example.com'}, function(err, user) {
+          if (err) { done(err); }
+
+          Page.findOne({path: '/user/anonymous/memo'}, function(err, page) {
+            expect(page.isCreator(user)).to.be.equal(true);
+            done();
+          })
+        });
+      });
+    });
+
+    context('with non-creator', function() {
+      it('should return false', function(done) {
+        User.findOne({email: 'anonymous1@example.com'}, function(err, user) {
+          if (err) { done(err); }
+
+          Page.findOne({path: '/user/anonymous/memo'}, function(err, page) {
+            expect(page.isCreator(user)).to.be.equal(false);
+            done();
+          })
+        });
+      });
+    });
+  });
+
   describe('.isGrantedFor', function() {
     context('with a granted user', function() {
       it('should return true', function(done) {
-        User.find({userId: 'anonymous'}, function(err, user) {
+        User.findOne({email: 'anonymous0@example.com'}, function(err, user) {
           if (err) { done(err); }
 
           Page.findOne({path: '/user/anonymous/memo'}, function(err, page) {
             if (err) { done(err); }
 
-            page.grantedUsers.push(user.id);
-
-            page.save(function(err, newPage) {
-              if (err) { done(err); }
-
-              expect(newPage.isGrantedFor(user)).to.be.equal(true);
-              done();
-            });
+            expect(page.isGrantedFor(user)).to.be.equal(true);
+            done();
           });
         });
       });
@@ -112,10 +169,10 @@ describe('Page', function () {
 
     context('with a public page', function() {
       it('should return true', function(done) {
-        User.find({userId: 'anonymous'}, function(err, user) {
+        User.findOne({email: 'anonymous1@example.com'}, function(err, user) {
           if (err) { done(err); }
 
-          Page.findOne({path: '/user/anonymous/memo'}, function(err, page) {
+          Page.findOne({path: '/grant/public'}, function(err, page) {
             if (err) { done(err); }
 
             expect(page.isGrantedFor(user)).to.be.equal(true);
@@ -127,7 +184,7 @@ describe('Page', function () {
 
     context('with a restricted page and an user who has no grant', function() {
       it('should return false', function(done) {
-        User.find({userId: 'anonymous'}, function(err, user) {
+        User.findOne({email: 'anonymous1@example.com'}, function(err, user) {
           if (err) { done(err); }
 
           Page.findOne({path: '/grant/restricted'}, function(err, page) {