Răsfoiți Sursa

Implement file upload (with only nominal scenarios yet)

Sotaro KARASAWA 10 ani în urmă
părinte
comite
8fb5fa71c4

+ 2 - 1
bower.json

@@ -25,6 +25,7 @@
     "marked": "~0.3.3",
     "reveal.js": "~3.0.0",
     "jquery": "~2.1.3",
-    "highlightjs": "~8.4.0"
+    "highlightjs": "~8.4.0",
+    "inline-attachment": "~2.0.1"
   }
 }

+ 2 - 0
gulpfile.js

@@ -36,6 +36,8 @@ var js = {
   src: [
     'bower_components/jquery/dist/jquery.js',
     'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js',
+    'bower_components/inline-attachment/src/inline-attachment.js',
+    'bower_components/inline-attachment/src/jquery.inline-attachment.js',
     'node_modules/socket.io-client/socket.io.js',
     'bower_components/marked/lib/marked.js',
     'bower_components/jquery.cookie/jquery.cookie.js',

+ 81 - 0
lib/models/attachment.js

@@ -0,0 +1,81 @@
+module.exports = function(crowi) {
+  var debug = require('debug')('crowi:models:attachment')
+    , mongoose = require('mongoose')
+    , ObjectId = mongoose.Schema.Types.ObjectId
+    , Promise = require('bluebird')
+  ;
+
+  function generateFileHash (fileName) {
+    var hasher = require('crypto').createHash('md5');
+    hasher.update(fileName);
+
+    return hasher.digest('hex');
+  }
+
+  attachmentSchema = new mongoose.Schema({
+    page: { type: ObjectId, ref: 'Page' },
+    creator: { type: ObjectId, ref: 'User' },
+    filePath: { type: String, required: true },
+    fileName: { type: String, required: true },
+    fileFormat: { type: String, required: true },
+    createdAt: { type: Date, default: Date.now }
+  });
+
+  attachmentSchema.statics.getListByPageId = function(id) {
+    var self = this;
+
+    return new Promise(function(resolve, reject) {
+
+      self
+        .find({page: id})
+        .sort('updatedAt', -1)
+        .exec(function(err, data) {
+          if (err) {
+            return reject(err);
+          }
+
+          if (data.length < 1) {
+            return resolve([]);
+          }
+
+          return data.populate(
+            [{path: 'creator', model: 'User'}],
+            function (err, populatedData) {
+              return resolve(populatedData);
+            }
+          );
+        });
+    });
+  };
+
+  attachmentSchema.statics.create = function(pageId, creator, filePath, fileName, fileFormat) {
+    var Attachment = this;
+
+    return new Promise(function(resolve, reject) {
+      var newAttachment = new Attachment();
+
+      newAttachment.page = pageId;
+      newAttachment.creator = creator._id;
+      newAttachment.filePath = filePath;
+      newAttachment.fileName = fileName;
+      newAttachment.fileFormat = fileFormat;
+      newAttachment.createdAt = Date.now();
+
+      newAttachment.save(function(err, data) {
+        debug('Attachment saved.', data);
+        if (err) {
+          return reject(err);
+        }
+        return resolve(data);
+      });
+    });
+  };
+
+  attachmentSchema.statics.createAttachmentFilePath = function (pageId, fileName, fileType) {
+    var ext = '.' + fileName.match(/(.*)(?:\.([^.]+$))/)[2];
+
+    return 'attachment/' + pageId + '/' + generateFileHash(fileName) + ext;
+  };
+
+  return mongoose.model('Attachment', attachmentSchema);
+};

+ 1 - 0
lib/models/index.js

@@ -3,4 +3,5 @@ module.exports = {
   User: require('./user'),
   Revision: require('./revision'),
   Bookmark: require('./bookmark'),
+  Attachment: require('./attachment'),
 };

+ 167 - 0
lib/models/npm-debug.log

@@ -0,0 +1,167 @@
+0 info it worked if it ends with ok
+1 verbose cli [ '/home/sotarok/.nvm/versions/node/v0.12.7/bin/node',
+1 verbose cli   '/home/sotarok/.nvm/versions/node/v0.12.7/bin/npm',
+1 verbose cli   'install',
+1 verbose cli   'inline-attachment' ]
+2 info using npm@2.11.3
+3 info using node@v0.12.7
+4 verbose install initial load of /home/sotarok/working/crowi/crowi/package.json
+5 warn package.json crowi@1.2.0 license should be a valid SPDX license expression
+6 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/async/package.json
+7 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/aws-sdk/package.json
+8 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/babel-core/package.json
+9 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/babel-loader/package.json
+10 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/basic-auth-connect/package.json
+11 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/bluebird/package.json
+12 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/body-parser/package.json
+13 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/bower/package.json
+14 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/chai/package.json
+15 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/cli/package.json
+16 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/connect-flash/package.json
+17 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/connect-redis/package.json
+18 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/consolidate/package.json
+19 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/cookie-parser/package.json
+20 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/debug/package.json
+21 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/errorhandler/package.json
+22 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/event-stream/package.json
+23 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/express/package.json
+24 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/express-form/package.json
+25 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/express-session/package.json
+26 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/facebook-node-sdk/package.json
+27 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/fluxible/package.json
+28 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/fluxible-addons-react/package.json
+29 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/fluxxor/package.json
+30 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/googleapis/package.json
+31 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp/package.json
+32 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-concat/package.json
+33 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-cssmin/package.json
+34 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-jshint/package.json
+35 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-rename/package.json
+36 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-sass/package.json
+37 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-spawn-mocha/package.json
+38 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-uglify/package.json
+39 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/gulp-watch/package.json
+40 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/jshint-stylish/package.json
+41 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/jsx-loader/package.json
+42 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/method-override/package.json
+43 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/mocha/package.json
+44 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/mongoose/package.json
+45 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/mongoose-paginate/package.json
+46 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/morgan/package.json
+47 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/multer/package.json
+48 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/node-libs-browser/package.json
+49 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/nodemailer/package.json
+50 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/nodemailer-ses-transport/package.json
+51 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/proxyquire/package.json
+52 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/react/package.json
+53 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/react-bootstrap/package.json
+54 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/react-router/package.json
+55 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/redis/package.json
+56 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/rimraf/package.json
+57 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/sinon/package.json
+58 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/sinon-chai/package.json
+59 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/socket.io/package.json
+60 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/socket.io-client/package.json
+61 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/superagent/package.json
+62 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/swig/package.json
+63 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/time/package.json
+64 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/webpack/package.json
+65 verbose installManyTop reading scoped package data from /home/sotarok/working/crowi/crowi/node_modules/webpack-stream/package.json
+66 info package.json aws-sdk@2.0.31 No license field.
+67 info package.json bower@1.4.1 No license field.
+68 info package.json cli@0.6.6 No license field.
+69 info package.json connect-flash@0.1.1 No license field.
+70 info package.json connect-redis@2.1.0 No license field.
+71 info package.json consolidate@0.11.0 No license field.
+72 info package.json event-stream@3.3.1 No license field.
+73 info package.json express-form@0.12.4 No license field.
+74 info package.json facebook-node-sdk@0.1.10 license should be a valid SPDX license expression
+75 info package.json googleapis@0.4.7 license should be a valid SPDX license expression
+76 info package.json gulp@3.8.11 No license field.
+77 info package.json gulp-concat@2.5.2 No license field.
+78 info package.json gulp-jshint@1.10.0 No license field.
+79 info package.json gulp-rename@1.2.2 No license field.
+80 info package.json jsx-loader@0.13.2 license should be a valid SPDX license expression
+81 info package.json mongoose@3.8.34 No license field.
+82 info package.json mongoose-paginate@3.1.3 No license field.
+83 info package.json proxyquire@1.4.0 No license field.
+84 info package.json redis@0.12.1 No license field.
+85 info package.json sinon@1.14.1 No license field.
+86 info package.json socket.io@1.3.6 No license field.
+87 info package.json fluxible@0.5.1 No license field.
+88 verbose readDependencies loading dependencies from /home/sotarok/working/crowi/crowi/package.json
+89 silly cache add args [ 'inline-attachment', null ]
+90 verbose cache add spec inline-attachment
+91 silly cache add parsed spec { raw: 'inline-attachment',
+91 silly cache add   scope: null,
+91 silly cache add   name: 'inline-attachment',
+91 silly cache add   rawSpec: '',
+91 silly cache add   spec: '*',
+91 silly cache add   type: 'range' }
+92 silly addNamed inline-attachment@*
+93 verbose addNamed "*" is a valid semver range for inline-attachment
+94 silly addNameRange { name: 'inline-attachment', range: '*', hasData: false }
+95 silly mapToRegistry name inline-attachment
+96 silly mapToRegistry using default registry
+97 silly mapToRegistry registry https://registry.npmjs.org/
+98 silly mapToRegistry uri https://registry.npmjs.org/inline-attachment
+99 verbose addNameRange registry:https://registry.npmjs.org/inline-attachment not in flight; fetching
+100 verbose request uri https://registry.npmjs.org/inline-attachment
+101 verbose request no auth needed
+102 info attempt registry request try #1 at 3:35:01 PM
+103 verbose request id b6e1fea9fbf57566
+104 http request GET https://registry.npmjs.org/inline-attachment
+105 http 404 https://registry.npmjs.org/inline-attachment
+106 verbose headers { 'content-type': 'application/json',
+106 verbose headers   'cache-control': 'max-age=0',
+106 verbose headers   'content-length': '2',
+106 verbose headers   'accept-ranges': 'bytes',
+106 verbose headers   date: 'Wed, 07 Oct 2015 07:31:09 GMT',
+106 verbose headers   via: '1.1 varnish',
+106 verbose headers   age: '0',
+106 verbose headers   connection: 'keep-alive',
+106 verbose headers   'x-served-by': 'cache-nrt6120-NRT',
+106 verbose headers   'x-cache': 'MISS',
+106 verbose headers   'x-cache-hits': '0',
+106 verbose headers   'x-timer': 'S1444203069.166386,VS0,VE202' }
+107 silly get cb [ 404,
+107 silly get   { 'content-type': 'application/json',
+107 silly get     'cache-control': 'max-age=0',
+107 silly get     'content-length': '2',
+107 silly get     'accept-ranges': 'bytes',
+107 silly get     date: 'Wed, 07 Oct 2015 07:31:09 GMT',
+107 silly get     via: '1.1 varnish',
+107 silly get     age: '0',
+107 silly get     connection: 'keep-alive',
+107 silly get     'x-served-by': 'cache-nrt6120-NRT',
+107 silly get     'x-cache': 'MISS',
+107 silly get     'x-cache-hits': '0',
+107 silly get     'x-timer': 'S1444203069.166386,VS0,VE202' } ]
+108 verbose stack Error: Registry returned 404 for GET on https://registry.npmjs.org/inline-attachment
+108 verbose stack     at CachingRegistryClient.<anonymous> (/home/sotarok/.nvm/versions/node/v0.12.7/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:239:14)
+108 verbose stack     at Request._callback (/home/sotarok/.nvm/versions/node/v0.12.7/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:170:14)
+108 verbose stack     at Request.self.callback (/home/sotarok/.nvm/versions/node/v0.12.7/lib/node_modules/npm/node_modules/request/request.js:354:22)
+108 verbose stack     at Request.emit (events.js:110:17)
+108 verbose stack     at Request.<anonymous> (/home/sotarok/.nvm/versions/node/v0.12.7/lib/node_modules/npm/node_modules/request/request.js:1207:14)
+108 verbose stack     at Request.emit (events.js:129:20)
+108 verbose stack     at IncomingMessage.<anonymous> (/home/sotarok/.nvm/versions/node/v0.12.7/lib/node_modules/npm/node_modules/request/request.js:1153:12)
+108 verbose stack     at IncomingMessage.emit (events.js:129:20)
+108 verbose stack     at _stream_readable.js:908:16
+108 verbose stack     at process._tickCallback (node.js:355:11)
+109 verbose statusCode 404
+110 verbose pkgid inline-attachment
+111 verbose cwd /home/sotarok/working/crowi/crowi/lib/models
+112 error Linux 3.2.0-4-amd64
+113 error argv "/home/sotarok/.nvm/versions/node/v0.12.7/bin/node" "/home/sotarok/.nvm/versions/node/v0.12.7/bin/npm" "install" "inline-attachment"
+114 error node v0.12.7
+115 error npm  v2.11.3
+116 error code E404
+117 error 404 Registry returned 404 for GET on https://registry.npmjs.org/inline-attachment
+117 error 404
+117 error 404 'inline-attachment' is not in the npm registry.
+117 error 404 You should bug the author to publish it (or use the name yourself!)
+117 error 404 It was specified as a dependency of 'crowi'
+117 error 404
+117 error 404 Note that you can also install from a
+117 error 404 tarball, folder, http url, or git url.
+118 verbose exit [ 1, true ]

+ 104 - 0
lib/routes/attachment.js

@@ -0,0 +1,104 @@
+module.exports = function(crowi, app) {
+  'use strict';
+
+  var debug = require('debug')('crowi:routs:attachment')
+    ,  Attachment = crowi.model('Attachment')
+    , User = crowi.model('User')
+    , fs = require('fs')
+    , actions = {}
+    , api = {};
+
+  actions.api = api;
+
+  api.list = function(req, res){
+    var id = req.params.pageId;
+
+    Attachment.getListByPageId(id)
+    .then(function(attachments) {
+      res.json({
+        status: true,
+        data: {
+          attachments: attachments
+        }
+      });
+    });
+  };
+
+  /**
+   *
+   */
+  api.add = function(req, res){
+    var id = req.params.pageId;
+    if (id == 0) {
+      // TODO create page before process upload
+    }
+
+    var fileUploader = require('../util/fileUploader')(crowi, app);
+    var tmpFile = req.files.file || null;
+    if (!tmpFile) {
+      return res.json({
+        status: false,
+        message: 'File error.'
+      });
+    }
+
+    var tmpPath = tmpFile.path,
+      fileName = tmpFile.name,
+      fileType = tmpFile.mimetype,
+      filePath = Attachment.createAttachmentFilePath(id, fileName, fileType);
+
+    fileUploader.uploadFile(
+      filePath,
+      fileType,
+      fs.createReadStream(tmpPath, {
+        flags: 'r',
+        encoding: null,
+        fd: null,
+        mode: '0666',
+        autoClose: true
+      }),
+      {})
+    .then(function(data) {
+
+      Attachment.create(id, req.user, filePath, fileName, fileType)
+      .then(function(data) {
+        debug('Succesfully save attachment data', data);
+
+        var imageUrl = fileUploader.generateS3FileUrl(data.filePath);
+        return res.json({
+          status: true,
+          filename: imageUrl,
+          attachment: data,
+          message: 'Successfully uploaded.',
+        });
+      }, function (err) {
+        debug('Error on saving attachment data', err);
+
+        return res.json({
+          status: false,
+          message: '',
+        });
+      }).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.',
+      });
+    });
+  };
+
+  api.remove = function(req, res){
+    var id = req.params.id;
+  };
+
+  return actions;
+};

+ 22 - 18
lib/routes/index.js

@@ -8,6 +8,7 @@ module.exports = function(crowi, app) {
     , admin     = require('./admin')(crowi, app)
     , installer = require('./installer')(crowi, app)
     , user      = require('./user')(crowi, app)
+    , attachment= require('./attachment')(crowi, app)
     , loginRequired = middleware.loginRequired
     ;
 
@@ -49,25 +50,28 @@ module.exports = function(crowi, app) {
   app.post('/admin/user/:id/remove'     , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.remove);
   app.post('/admin/user/:id/removeCompletely' , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.removeCompletely);
 
-  app.get('/me'                      , loginRequired(crowi, app) , me.index);
-  app.get('/me/password'             , loginRequired(crowi, app) , me.password);
-  app.post('/me'                     , form.me.user               , loginRequired(crowi, app) , me.index);
-  app.post('/me/password'            , form.me.password           , loginRequired(crowi, app) , me.password);
-  app.post('/me/picture/delete'      , loginRequired(crowi, app) , me.deletePicture);
-  app.post('/me/auth/facebook'       , loginRequired(crowi, app) , me.authFacebook);
-  app.post('/me/auth/google'         , loginRequired(crowi, app) , me.authGoogle);
-  app.get('/me/auth/google/callback' , loginRequired(crowi, app) , me.authGoogleCallback);
+  app.get('/me'                       , loginRequired(crowi, app) , me.index);
+  app.get('/me/password'              , loginRequired(crowi, app) , me.password);
+  app.post('/me'                      , form.me.user              , loginRequired(crowi, app) , me.index);
+  app.post('/me/password'             , form.me.password          , loginRequired(crowi, app) , me.password);
+  app.post('/me/picture/delete'       , loginRequired(crowi, app) , me.deletePicture);
+  app.post('/me/auth/facebook'        , loginRequired(crowi, app) , me.authFacebook);
+  app.post('/me/auth/google'          , loginRequired(crowi, app) , me.authGoogle);
+  app.get( '/me/auth/google/callback' , loginRequired(crowi, app) , me.authGoogleCallback);
 
-  app.get('/:id([0-9a-z]{24})'       , loginRequired(crowi, app) , page.api.redirector);
-  app.get('/_r/:id([0-9a-z]{24})'    , loginRequired(crowi, app) , page.api.redirector); // alias
-  app.get('/_api/check_username'     , user.api.checkUsername);
-  app.post('/_api/me/picture/upload' , loginRequired(crowi, app) , me.api.uploadPicture);
-  app.get('/_api/user/bookmarks'     , loginRequired(crowi, app) , user.api.bookmarks);
-  app.post('/_api/page_rename/*'     , loginRequired(crowi, app) , page.api.rename);
-  app.post('/_api/page/:id/like'     , loginRequired(crowi, app) , page.api.like);
-  app.post('/_api/page/:id/unlike'   , loginRequired(crowi, app) , page.api.unlike);
-  app.get('/_api/page/:id/bookmark'  , loginRequired(crowi, app) , page.api.isBookmarked);
-  app.post('/_api/page/:id/bookmark' , loginRequired(crowi, app) , page.api.bookmark);
+  app.get( '/:id([0-9a-z]{24})'       , loginRequired(crowi, app) , page.api.redirector);
+  app.get( '/_r/:id([0-9a-z]{24})'    , loginRequired(crowi, app) , page.api.redirector); // alias
+  app.get( '/_api/check_username'     , user.api.checkUsername);
+  app.post('/_api/me/picture/upload'  , loginRequired(crowi, app) , me.api.uploadPicture);
+  app.get( '/_api/user/bookmarks'     , loginRequired(crowi, app) , user.api.bookmarks);
+  app.post('/_api/page_rename/*'      , loginRequired(crowi, app) , page.api.rename);
+  app.get( '/_api/attachment/page/:pageId', loginRequired(crowi, app) , attachment.api.list);
+  app.post('/_api/attachment/page/:pageId', loginRequired(crowi, app) , attachment.api.add);
+  app.post('/_api/attachment/:id/remove',loginRequired(crowi, app), attachment.api.remove);
+  app.post('/_api/page/:id/like'      , loginRequired(crowi, app) , page.api.like);
+  app.post('/_api/page/:id/unlike'    , loginRequired(crowi, app) , page.api.unlike);
+  app.get( '/_api/page/:id/bookmark'  , loginRequired(crowi, app) , page.api.isBookmarked);
+  app.post('/_api/page/:id/bookmark'  , loginRequired(crowi, app) , page.api.bookmark);
   //app.get('/_api/page/*'           , user.useUserData()         , page.api.get);
   //app.get('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);

+ 25 - 3
lib/util/fileUploader.js

@@ -8,6 +8,7 @@ module.exports = function(crowi, app) {
 
   var aws = require('aws-sdk')
     , debug = require('debug')('crowi:lib:fileUploader')
+    , Promise = require('bluebird')
     , config = crowi.getConfig()
     , lib = {}
     ;
@@ -22,12 +23,27 @@ module.exports = function(crowi, app) {
     };
   }
 
+  function isUploadable(awsConfig) {
+    if (!awsConfig.accessKeyId ||
+        !awsConfig.secretAccessKey ||
+        !awsConfig.region ||
+        !awsConfig.bucket) {
+      return false;
+    }
+
+    return true;
+  }
+
   // lib.deleteFile = function(filePath, callback) {
   //   // TODO 実装する
   // };
+  //
 
-  lib.uploadFile = function(filePath, contentType, fileStream, options, callback) {
+  lib.uploadFile = function(filePath, contentType, fileStream, options) {
     var awsConfig = getAwsConfig();
+    if (!isUploadable(awsConfig)) {
+      return new Promise.reject(new Error('AWS is not configured.'));
+    }
 
     aws.config.update({
       accessKeyId: awsConfig.accessKeyId,
@@ -42,8 +58,14 @@ module.exports = function(crowi, app) {
     params.Body = fileStream;
     params.ACL = 'public-read';
 
-    s3.putObject(params, function(err, data) {
-      callback(err, data);
+    return new Promise(function(resolve, reject) {
+      s3.putObject(params, function(err, data) {
+        if (err) {
+          return reject(err);
+        }
+
+        return resolve(data);
+      });
     });
   };
 

+ 27 - 6
lib/views/_form.html

@@ -15,22 +15,31 @@
     <input type="hidden" name="pageForm[format]" value="markdown" id="form-format">
     <input type="hidden" name="pageForm[currentRevision]" value="{{ pageForm.currentRevision|default(revision._id.toString()) }}">
     <div class="form-submit-group form-group form-inline">
-      <select name="pageForm[grant]" class="form-control">
-        {% for grantId, grantLabel in consts.pageGrants %}
-        <option value="{{ grantId }}" {% if pageForm.grant|default(page.grant) == grantId %}selected{% endif %}>{{ grantLabel }}</option>
-        {% endfor %}
-      </select>
+      {#<button class="btn btn-default">
+        <i class="fa fa-file-text"></i>
+        ファイルを追加 ...
+      </button>#}
 
-      <input type="submit" class="btn btn-primary" id="edit-form-submit" value="ページを更新" />
+      <div class="pull-right">
+        <select name="pageForm[grant]" class="form-control">
+          {% for grantId, grantLabel in consts.pageGrants %}
+          <option value="{{ grantId }}" {% if pageForm.grant|default(page.grant) == grantId %}selected{% endif %}>{{ grantLabel }}</option>
+          {% endfor %}
+        </select>
+        <input type="submit" class="btn btn-primary" id="edit-form-submit" value="ページを更新" />
+      </div>
     </div>
   </form>
   <div class="col-md-6">
     <div id="preview-body" class="wiki preview-body">
     </div>
   </div>
+  <div class="file-module hidden">
+  </div>
   <script type="text/javascript">
   $(function() {
     // preview watch
+    var originalContent = $('#form-body').val();
     var prevContent = "";
     var watchTimer = setInterval(function() {
       var content = $('#form-body').val();
@@ -64,7 +73,19 @@
         self.blur();
       }
     });
+
+    var pageId = $('#content-main').data('page-id') || 0;
+    console.log("pageId", pageId);
+    var attachmentOption = {
+      uploadUrl: '/_api/attachment/page/' + pageId,
+      extraParams: {},
+      dataProcessor: function(data) {
+        console.log(data);
+      }
+    };
+    $('textarea#form-body').inlineattachment(attachmentOption);
   });
 
+
   </script>
 </div>

+ 1 - 1
lib/views/layout/2column.html

@@ -141,7 +141,7 @@
 {% endblock %} {# layout_sidebar #}
 
 {% block layout_main %}
-<div id="main" class="main col-md-9 {% if page %}{{ css.grant(page) }}{% endif %}" ng-controller="WikiPageController">
+<div id="main" class="main col-md-9 {% if page %}{{ css.grant(page) }}{% endif %}">
   {% if page && page.grant != 1 %}
   <p class="page-grant">
     <i class="fa fa-lock"></i> {{ consts.pageGrants[page.grant] }} (このページの閲覧は制限されています)

+ 1 - 1
lib/views/page.html

@@ -13,7 +13,7 @@
 {% endblock %}
 
 {% block content_main %}
-<div class="content-main {% if not page %}on-edit{% endif %}">
+<div id="content-main" class="content-main {% if not page %}on-edit{% endif %}" data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}">
 
   {% if not page %}
   <ul class="nav nav-tabs hidden-print">