spaces.js 16.1 KB
Newer Older
mntmn's avatar
mntmn committed
1
2
"use strict";
var config = require('config');
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
3
const os = require('os');
4
5
6
7
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
mntmn's avatar
mntmn committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom');
var payloadConverter = require('../../helpers/artifact_converter');

var slug = require('slug');

var fs = require('fs');
var async = require('async');
var _ = require("underscore");
var request = require('request');
var url = require("url");
var path = require("path");
var crypto = require('crypto');
var glob = require('glob');
var gm = require('gm');
const exec = require('child_process');
var express = require('express');
var router = express.Router();

// JSON MAPPINGS
var userMapping = {
  _id: 1,
  nickname: 1,
  email: 1,
  avatar_thumb_uri: 1
};

var spaceMapping = {
  _id: 1,
  name: 1,
  thumbnail_url: 1
};

router.get('/', function(req, res, next) {
  if (!req.user) {
    res.status(403).json({
      error: "auth required"
    });
  } else {
    if (req.query.writablefolders) {
52
53
54
      db.Membership.find({where: {
        user_id: req.user._id
      }}, (memberships) => {
mntmn's avatar
mntmn committed
55
56
        
        var validMemberships = memberships.filter((m) => {
57
          if (!m.space_id || (m.space_id == "undefined"))
mntmn's avatar
mntmn committed
58
            return false;
59
          return true;
mntmn's avatar
mntmn committed
60
61
62
63
64
65
66
        });

        var editorMemberships = validMemberships.filter((m) => {
          return (m.role == "editor") || (m.role == "admin")
        });

        var spaceIds = editorMemberships.map(function(m) {
67
          return m.space_id;
mntmn's avatar
mntmn committed
68
69
        });

70
        // TODO port
mntmn's avatar
mntmn committed
71
72
73
74
75
76
77
78
79
80
81
82
83
84
        var q = {
          "space_type": "folder",
          "$or": [{
            "creator": req.user._id
          }, {
            "_id": {
              "$in": spaceIds
            },
            "creator": {
              "$ne": req.user._id
            }
          }]
        };

85
86
87
        db.Space
          .findAll({where: q})
          .then(function(spaces) {
mntmn's avatar
mntmn committed
88
            var updatedSpaces = spaces.map(function(s) {
89
              var spaceObj = s; //.toObject();
mntmn's avatar
mntmn committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
              return spaceObj;
            });

            async.map(spaces, (space, cb) => {
              Space.getRecursiveSubspacesForSpace(space, (err, spaces) => {
                var allSpaces = spaces;
                cb(err, allSpaces);
              })
            }, (err, spaces) => {

              var allSpaces = _.flatten(spaces);

              var onlyFolders = _.filter(allSpaces, (s) => {
                return s.space_type == "folder";
              })
              var uniqueFolders = _.unique(onlyFolders, (s) => {
106
                return s._id;
mntmn's avatar
mntmn committed
107
108
109
110
111
112
113
114
              })

              res.status(200).json(uniqueFolders);
            });
          });
      });
    } else if (req.query.search) {

115
116
117
      db.Membership.findAll({where:{
        user_id: req.user._id
      }}).then(memberships => {
mntmn's avatar
mntmn committed
118
119
        
        var validMemberships = memberships.filter(function(m) {
120
          if (!m.space_id || (m.space_id == "undefined"))
mntmn's avatar
mntmn committed
121
122
            return false;
          else
123
            return true;
mntmn's avatar
mntmn committed
124
125
126
        });

        var spaceIds = validMemberships.map(function(m) {
127
          return m.space_id;
mntmn's avatar
mntmn committed
128
129
        });

130
131
132
133
134
135
136
137
138
139
140
        // TODO FIXME port
        var q = { where: {
          [Op.or]: [{"creator_id": req.user._id},
                   {"_id": {[Op.in]: spaceIds}},
                   {"parent_space_id": {[Op.in]: spaceIds}}],
          name: {[Op.like]: "%"+req.query.search+"%"}
        }, include: ['creator']};

        db.Space
          .findAll(q)
          .then(function(spaces) {
mntmn's avatar
mntmn committed
141
142
143
144
145
146
            res.status(200).json(spaces);
          });
      });

    } else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {

147
148
      db.Space
        .findOne({where: {
mntmn's avatar
mntmn committed
149
          _id: req.query.parent_space_id
150
151
152
        }})
        //.populate('creator', userMapping)
        .then(function(space) {
mntmn's avatar
mntmn committed
153
          if (space) {
154
            db.getUserRoleInSpace(space, req.user, function(role) {
mntmn's avatar
mntmn committed
155
              if (role == "none") {
156
                if (space.access_mode == "public") {
mntmn's avatar
mntmn committed
157
158
159
160
161
                  role = "viewer";
                }
              }

              if (role != "none") {
162
163
                db.Space
                  .findAll({where:{
mntmn's avatar
mntmn committed
164
                    parent_space_id: req.query.parent_space_id
165
166
                  }, include:['creator']})
                  .then(function(spaces) {
mntmn's avatar
mntmn committed
167
168
169
170
171
172
173
174
175
176
177
178
                    res.status(200).json(spaces);
                  });
              } else {
                res.status(403).json({"error": "no authorized"});
              }
            });
          } else {
            res.status(404).json({"error": "space not found"});
          }
        });

    } else {
179
180
181
182
183
      db.Membership.findAll({ where: {
        user_id: req.user._id
      }}).then(memberships => {
        if (!memberships) memberships = [];
        
mntmn's avatar
mntmn committed
184
        var validMemberships = memberships.filter(function(m) {
185
          if (!m.space_id || (m.space_id == "undefined"))
mntmn's avatar
mntmn committed
186
187
188
189
            return false;
        });

        var spaceIds = validMemberships.map(function(m) {
190
          return m.space_id;
mntmn's avatar
mntmn committed
191
192
193
        });

        var q = {
194
195
          [Op.or]: [{
            "creator_id": req.user._id,
mntmn's avatar
mntmn committed
196
197
198
            "parent_space_id": req.user.home_folder_id
          }, {
            "_id": {
199
              [Op.in]: spaceIds
mntmn's avatar
mntmn committed
200
            },
201
202
            "creator_id": {
              [Op.ne]: req.user._id
mntmn's avatar
mntmn committed
203
204
205
206
            }
          }]
        };

207
208
209
        db.Space
          .findAll({where: q, include: ['creator']})
          .then(function(spaces) {
mntmn's avatar
mntmn committed
210
            var updatedSpaces = spaces.map(function(s) {
211
              var spaceObj = db.spaceToObject(s);
mntmn's avatar
mntmn committed
212
213
214
215
216
217
218
219
220
              return spaceObj;
            });
            res.status(200).json(spaces);
          });
      });
    }
  }
});

221
// create a space
mntmn's avatar
mntmn committed
222
223
224
225
226
router.post('/', function(req, res, next) {
  if (req.user) {
    var attrs = req.body;

    var createSpace = () => {
227
228
      attrs._id = uuidv4();
      attrs.creator_id = req.user._id;
mntmn's avatar
mntmn committed
229
230
231
      attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
      attrs.edit_slug = slug(attrs.name);
      
232
233
234
235
236
237
238
239
240
241
242
243
      db.Space.create(attrs).then(createdSpace => {
        //if (err) res.sendStatus(400);
        var membership = {
          _id: uuidv4(),
          user_id: req.user._id,
          space_id: attrs._id,
          role: "admin"
        };
        
        db.Membership.create(membership).then(() => {
          res.status(201).json(createdSpace);
        });
mntmn's avatar
mntmn committed
244
245
246
247
      });
    }

    if (attrs.parent_space_id) {
248
      db.Space.findOne({ where: {
mntmn's avatar
mntmn committed
249
        "_id": attrs.parent_space_id
250
      }}).then(parentSpace => {
mntmn's avatar
mntmn committed
251
        if (parentSpace) {
252
          db.getUserRoleInSpace(parentSpace, req.user, (role) => {
mntmn's avatar
mntmn committed
253
254
255
256
            if ((role == "editor") || (role == "admin")) {
              createSpace();
            } else {
              res.status(403).json({
257
                "error": "not editor in parent Space. role: "+role
mntmn's avatar
mntmn committed
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
              });
            }
          });
        } else {
          res.status(404).json({
            "error": "parent Space not found"
          });
        }
      });
    } else {
      createSpace();
    }

  } else {
    res.sendStatus(403);
  }
});

router.get('/:id', function(req, res, next) {
  res.status(200).json(req.space);
});

280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
router.get('/:id/path', (req, res) => {
  // build up a breadcrumb trail (path)
  var path = [];
  var buildPath = (space) => {
    if (space.parent_space_id) {
      db.Space.findOne({ where: {
        "_id": space.parent_space_id
      }}).then(parentSpace => {
        if (space._id == parentSpace._id) {
          console.error("error: circular parent reference for space " + space._id);
          res.send("error: circular reference");
        } else {
          path.push(parentSpace);
          buildPath(parentSpace);
        }
      });
    } else {
      // reached the top
      res.json(path.reverse());
    }
  }
  buildPath(req.space);
});

mntmn's avatar
mntmn committed
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
router.put('/:id', function(req, res) {
  var space = req.space;
  var newAttr = req.body;

  if (req['spaceRole'] != "editor" && req['spaceRole'] != "admin") {
    res.sendStatus(403);
    return;
  }

  newAttr.updated_at = new Date();
  newAttr.edit_slug = slug(newAttr['name']);

  delete newAttr['_id'];
  delete newAttr['editor_name'];
  delete newAttr['creator'];

320
  db.Space.update(newAttr, {where: {
mntmn's avatar
mntmn committed
321
    "_id": space._id
322
323
  }}).then(space => {
    res.distributeUpdate("Space", space);
mntmn's avatar
mntmn committed
324
325
326
327
328
329
  });
});

router.post('/:id/background', function(req, res, next) {
  var space = req.space;
  var newDate = new Date();
330
  var fileName = (req.query.filename || "upload.jpg").replace(/[^a-zA-Z0-9\.]/g, '');
mntmn's avatar
mntmn committed
331
332
333
334
335
336
337
338
  var localFilePath = "/tmp/" + fileName;
  var writeStream = fs.createWriteStream(localFilePath);
  var stream = req.pipe(writeStream);

  req.on('end', function() {
    uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) {
      if (err) res.status(400).json(err);
      else {
339
340
        if (space.background_uri) {
          var oldPath = url.parse(req.space.background_uri).pathname;
mntmn's avatar
mntmn committed
341
          uploader.removeFile(oldPath, function(err) {
342
            console.error("removed old bg error:", err);
mntmn's avatar
mntmn committed
343
344
345
          });
        }

346
347
        db.Space.update({
          background_uri: backgroundUrl
mntmn's avatar
mntmn committed
348
        }, {
349
350
351
352
353
354
355
356
357
358
          where: { "_id": space._id }
        }, function(rows) {
          fs.unlink(localFilePath, function(err) {
            if (err) {
              console.error(err);
              res.status(400).json(err);
            } else {
              res.status(200).json(space);
            }
          });
mntmn's avatar
mntmn committed
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
        });
      }
    });
  });
});

var handleDuplicateSpaceRequest = function(req, res, parentSpace) {
  Space.duplicateSpace(req.space, req.user, 0, (err, newSpace) => {
    if (err) {
      console.error(err);
      res.status(400).json(err);
    } else {
      res.status(201).json(newSpace);
    }
  }, parentSpace);
}

router.post('/:id/duplicate', (req, res, next) => {
  if (req.query.parent_space_id) {
    Space.findOne({
      _id: req.query.parent_space_id
    }).populate('creator', userMapping).exec((err, parentSpace) => {
      if (!parentSpace) {
        res.status(404).json({
383
          "error": "parent space not found for duplicate"
mntmn's avatar
mntmn committed
384
385
        });
      } else {
386
        db.getUserRoleInSpace(parentSpace, req.user, (role) => {
mntmn's avatar
mntmn committed
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
          if (role == "admin" ||  role == "editor") {
            handleDuplicateSpaceRequest(req, res, parentSpace);
          } else {
            res.status(403).json({
              "error": "not authed for parent_space_id"
            });
          }
        });
      }
    });
  } else {
    handleDuplicateSpaceRequest(req, res);
  }
});

router.delete('/:id', function(req, res, next) {
  if (req.user) {
    const space = req.space;

    if (req.spaceRole == "admin") {
      const attrs = req.body;
408
409
      space.destroy().then(function() {
        res.distributeDelete("Space", space);
mntmn's avatar
mntmn committed
410
411
412
      });
    } else {
      res.status(403).json({
413
        "error": "requires admin role"
mntmn's avatar
mntmn committed
414
415
416
417
418
419
420
421
422
423
424
425
      });
    }
  } else {
    res.sendStatus(403);
  }
});

router.post('/:id/artifacts-pdf', function(req, res, next) {
  if (req.spaceRole == "editor" || req.spaceRole == "admin") {

    var withZones = (req.query.zones) ? req.query.zones == "true" : false;
    var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, '');
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
426
    var localFilePath = os.tmpdir() + "/" + fileName;
mntmn's avatar
mntmn committed
427
428
429
430
431
432
    var writeStream = fs.createWriteStream(localFilePath);
    var stream = req.pipe(writeStream);

    req.on('end', function() {

      var rawName = fileName.slice(0, fileName.length - 4);
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
433
434
435
      var outputFolder = os.tmpdir() + "/" + rawName;
      
      fs.mkdir(outputFolder, function(err) {
mntmn's avatar
mntmn committed
436
437
        var images = outputFolder + "/" + rawName + "-page-%03d.jpeg";
        
438
        // FIXME not portable
mntmn's avatar
mntmn committed
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
        exec.execFile("gs", ["-sDEVICE=jpeg", "-dDownScaleFactor=4", "-dDOINTERPOLATE", "-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-sOutputFile=" + images, "-r250", "-f", localFilePath], {}, function(error, stdout, stderr) {
          if (error === null) {

            glob(outputFolder + "/*.jpeg", function(er, files) {
              var count = files.length;
              var delta = 10;

              var limitPerRow = Math.ceil(Math.sqrt(count));

              var startX = parseInt(req.query.x, delta);
              var startY = parseInt(req.query.y, delta);

              async.mapLimit(files, 20, function(localfilePath, cb) {

                var fileName = path.basename(localfilePath);
                var baseName = path.basename(localfilePath, ".jpeg");

                var number = parseInt(baseName.slice(baseName.length - 3, baseName.length), 10);

Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
458
                gm(localFilePath).size((err, size) => {
mntmn's avatar
mntmn committed
459
460
461
462
463
464
465
                  var w = 350;
                  var h = w;

                  var x = startX + (((number - 1) % limitPerRow) * w);
                  var y = startY + ((parseInt(((number - 1) / limitPerRow), 10) + 1) * w);

                  var userId;
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
466
                  if (req.user) userId = req.user._id;
mntmn's avatar
mntmn committed
467

Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
468
469
                  var a = db.Artifact.create({
                    _id: uuidv4(),
mntmn's avatar
mntmn committed
470
471
472
473
                    mime: "image/jpg",
                    space_id: req.space._id,
                    user_id: userId,
                    editor_name: req.guest_name,
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
                    w: w,
                    h: h,
                    x: x,
                    y: y,
                    z: (number + (count + 100))
                  }).then(a => {
                    payloadConverter.convert(a, fileName, localfilePath, (error, artifact) => {
                      if (error) res.status(400).json(error);
                      else {
                        if (withZones) {
                          var zone = {
                            _id: uuidv4(),
                            mime: "x-spacedeck/zone",
                            description: "Zone " + (number),
                            space_id: req.space._id,
                            user_id: userId,
                            editor_name: req.guest_name,
mntmn's avatar
mntmn committed
491
492
493
494
                            w: artifact.board.w + 20,
                            h: artifact.board.h + 40,
                            x: x - 10,
                            y: y - 30,
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
495
                            z: number,
mntmn's avatar
mntmn committed
496
497
498
                            order: number,
                            valign: "middle",
                            align: "center"
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
499
                          };
mntmn's avatar
mntmn committed
500

Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
501
502
503
504
                          db.Artifact.create(zone,((z) => {
                            redis.sendMessage("create", "Artifact", zone.toJSON(), req.channelId);
                            cb(null, [artifact, zone]);
                          }));
mntmn's avatar
mntmn committed
505

Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
506
507
508
                        } else {
                          cb(null, [artifact]);
                        }
mntmn's avatar
mntmn committed
509
                      }
Lukas F. Hartmann's avatar
Lukas F. Hartmann committed
510
                    });
mntmn's avatar
mntmn committed
511
512
513
514
515
516
                  });

                });

              }, function(err, artifacts) {

517
                // FIXME not portable
mntmn's avatar
mntmn committed
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
                exec.execFile("rm", ["-r", outputFolder], function(err) {
                  res.status(201).json(_.flatten(artifacts));
                  
                  async.eachLimit(artifacts, 10, (artifact_or_artifacts, cb) => {

                    if (artifact_or_artifacts instanceof Array) {
                      _.each(artifact_or_artifacts, (a) => {
                        redis.sendMessage("create", "Artifact", a.toJSON(), req.channelId);
                      });
                    } else  {
                      redis.sendMessage("create", "Artifact", artifact_or_artifacts.toJSON(), req.channelId);
                    }
                    cb(null);
                  });
                });
              });
            });
          } else {
            console.error("error:", error);
537
            // FIXME not portable
mntmn's avatar
mntmn committed
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
            exec.execFile("rm", ["-r", outputFolder], function(err) {
              fs.unlink(localFilePath);
              res.status(400).json({});
            });
          }
        });
      });
    });
  } else {
    res.status(401).json({
      "error": "no access"
    });
  }
});

module.exports = router;