page.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. module.exports = function(crowi) {
  2. var debug = require('debug')('crowi:models:page')
  3. , mongoose = require('mongoose')
  4. , ObjectId = mongoose.Schema.Types.ObjectId
  5. , GRANT_PUBLIC = 1
  6. , GRANT_RESTRICTED = 2
  7. , GRANT_SPECIFIED = 3
  8. , GRANT_OWNER = 4
  9. , PAGE_GRANT_ERROR = 1
  10. , STATUS_WIP = 'wip'
  11. , STATUS_PUBLISHED = 'published'
  12. , STATUS_DELEDED = 'deleted'
  13. , STATUS_DEPRECATED = 'deprecated'
  14. , pageEvent = crowi.event('page')
  15. , pageSchema;
  16. function isPortalPath(path) {
  17. if (path.match(/.*\/$/)) {
  18. return true;
  19. }
  20. return false;
  21. }
  22. pageSchema = new mongoose.Schema({
  23. path: { type: String, required: true, index: true },
  24. revision: { type: ObjectId, ref: 'Revision' },
  25. redirectTo: { type: String, index: true },
  26. status: { type: String, default: STATUS_PUBLISHED, index: true },
  27. grant: { type: Number, default: GRANT_PUBLIC, index: true },
  28. grantedUsers: [{ type: ObjectId, ref: 'User' }],
  29. creator: { type: ObjectId, ref: 'User', index: true },
  30. liker: [{ type: ObjectId, ref: 'User', index: true }],
  31. seenUsers: [{ type: ObjectId, ref: 'User', index: true }],
  32. commentCount: { type: Number, default: 0 },
  33. extended: {
  34. type: String,
  35. default: '{}',
  36. get: function(data) {
  37. try {
  38. return JSON.parse(data);
  39. } catch(e) {
  40. return data;
  41. }
  42. },
  43. set: function(data) {
  44. return JSON.stringify(data);
  45. }
  46. },
  47. createdAt: { type: Date, default: Date.now },
  48. updatedAt: Date
  49. }, {
  50. toJSON: {getters: true},
  51. toObject: {getters: true}
  52. });
  53. pageEvent.on('create', pageEvent.onCreate);
  54. pageEvent.on('update', pageEvent.onUpdate);
  55. pageSchema.methods.isWIP = function() {
  56. return this.status === STATUS_WIP;
  57. };
  58. pageSchema.methods.isPublished = function() {
  59. // null: this is for B.C.
  60. return this.status === null || this.status === STATUS_PUBLISHED;
  61. };
  62. pageSchema.methods.isDeleted = function() {
  63. return this.status === STATUS_DELEDED;
  64. };
  65. pageSchema.methods.isDeprecated = function() {
  66. return this.status === STATUS_DEPRECATED;
  67. };
  68. pageSchema.methods.isPublic = function() {
  69. if (!this.grant || this.grant == GRANT_PUBLIC) {
  70. return true;
  71. }
  72. return false;
  73. };
  74. pageSchema.methods.isPortal = function() {
  75. return isPortalPath(this.path);
  76. };
  77. pageSchema.methods.isCreator = function(userData) {
  78. if (this.populated('creator') && this.creator._id.toString() === userData._id.toString()) {
  79. return true;
  80. } else if (this.creator.toString() === userData._id.toString()) {
  81. return true
  82. }
  83. return false;
  84. };
  85. pageSchema.methods.isGrantedFor = function(userData) {
  86. if (this.isPublic() || this.isCreator(userData)) {
  87. return true;
  88. }
  89. if (this.grantedUsers.indexOf(userData._id) >= 0) {
  90. return true;
  91. }
  92. return false;
  93. };
  94. pageSchema.methods.isLatestRevision = function() {
  95. // populate されていなくて判断できない
  96. if (!this.latestRevision || !this.revision) {
  97. return true;
  98. }
  99. return (this.latestRevision == this.revision._id.toString());
  100. };
  101. pageSchema.methods.isUpdatable = function(previousRevision) {
  102. var revision = this.latestRevision || this.revision;
  103. if (revision != previousRevision) {
  104. return false;
  105. }
  106. return true;
  107. };
  108. pageSchema.methods.isLiked = function(userData) {
  109. return this.liker.some(function(likedUser) {
  110. return likedUser == userData._id.toString();
  111. });
  112. };
  113. pageSchema.methods.like = function(userData) {
  114. var self = this,
  115. Page = self;
  116. return new Promise(function(resolve, reject) {
  117. var added = self.liker.addToSet(userData._id);
  118. if (added.length > 0) {
  119. self.save(function(err, data) {
  120. if (err) {
  121. return reject(err);
  122. }
  123. debug('liker updated!', added);
  124. return resolve(data);
  125. });
  126. } else {
  127. debug('liker not updated');
  128. return reject(self);
  129. }
  130. });
  131. };
  132. pageSchema.methods.unlike = function(userData, callback) {
  133. var self = this,
  134. Page = self;
  135. return new Promise(function(resolve, reject) {
  136. var beforeCount = self.liker.length;
  137. self.liker.pull(userData._id);
  138. if (self.liker.length != beforeCount) {
  139. self.save(function(err, data) {
  140. if (err) {
  141. return reject(err);
  142. }
  143. return resolve(data);
  144. });
  145. } else {
  146. debug('liker not updated');
  147. return reject(self);
  148. }
  149. });
  150. };
  151. pageSchema.methods.isSeenUser = function(userData) {
  152. var self = this,
  153. Page = self;
  154. return this.seenUsers.some(function(seenUser) {
  155. return seenUser.equals(userData._id);
  156. });
  157. };
  158. pageSchema.methods.seen = function(userData) {
  159. var self = this,
  160. Page = self;
  161. if (this.isSeenUser(userData)) {
  162. debug('seenUsers not updated');
  163. return Promise.resolve(this);
  164. }
  165. return new Promise(function(resolve, reject) {
  166. if (!userData || !userData._id) {
  167. reject(new Error('User data is not valid'));
  168. }
  169. var added = self.seenUsers.addToSet(userData);
  170. self.save(function(err, data) {
  171. if (err) {
  172. return reject(err);
  173. }
  174. debug('seenUsers updated!', added);
  175. return resolve(self);
  176. });
  177. });
  178. };
  179. pageSchema.methods.getSlackChannel = function() {
  180. var extended = this.get('extended');
  181. if (!extended) {
  182. return '';
  183. }
  184. return extended.slack || '';
  185. };
  186. pageSchema.methods.updateSlackChannel = function(slackChannel) {
  187. var extended = this.extended;
  188. extended.slack = slackChannel;
  189. return this.updateExtended(extended);
  190. };
  191. pageSchema.methods.updateExtended = function(extended) {
  192. var page = this;
  193. page.extended = extended;
  194. return new Promise(function(resolve, reject) {
  195. return page.save(function(err, doc) {
  196. if (err) {
  197. return reject(err);
  198. }
  199. return resolve(doc);
  200. });
  201. });
  202. };
  203. pageSchema.statics.populatePageData = function(pageData, revisionId) {
  204. var Page = crowi.model('Page');
  205. var User = crowi.model('User');
  206. pageData.latestRevision = pageData.revision;
  207. if (revisionId) {
  208. pageData.revision = revisionId;
  209. }
  210. pageData.likerCount = pageData.liker.length || 0;
  211. pageData.seenUsersCount = pageData.seenUsers.length || 0;
  212. return new Promise(function(resolve, reject) {
  213. pageData.populate([
  214. {path: 'creator', model: 'User', select: User.USER_PUBLIC_FIELDS},
  215. {path: 'revision', model: 'Revision'},
  216. //{path: 'liker', options: { limit: 11 }},
  217. //{path: 'seenUsers', options: { limit: 11 }},
  218. ], function (err, pageData) {
  219. Page.populate(pageData, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS}, function(err, data) {
  220. if (err) {
  221. return reject(err);
  222. }
  223. return resolve(data);
  224. });
  225. });
  226. });
  227. };
  228. pageSchema.statics.populatePageListToAnyObjects = function(pageIdObjectArray) {
  229. var Page = this;
  230. var pageIdMappings = {};
  231. var pageIds = pageIdObjectArray.map(function(page, idx) {
  232. if (!page._id) {
  233. throw new Error('Pass the arg of populatePageListToAnyObjects() must have _id on each element.');
  234. }
  235. pageIdMappings[String(page._id)] = idx;
  236. return page._id;
  237. });
  238. return new Promise(function(resolve, reject) {
  239. Page.findListByPageIds(pageIds, {limit: 100}) // limit => if the pagIds is greater than 100, ignore
  240. .then(function(pages) {
  241. pages.forEach(function(page) {
  242. Object.assign(pageIdObjectArray[pageIdMappings[String(page._id)]], page._doc);
  243. });
  244. resolve(pageIdObjectArray);
  245. });
  246. });
  247. };
  248. pageSchema.statics.updateCommentCount = function (page, num)
  249. {
  250. var self = this;
  251. return new Promise(function(resolve, reject) {
  252. self.update({_id: page}, {commentCount: num}, {}, function(err, data) {
  253. if (err) {
  254. debug('Update commentCount Error', err);
  255. return reject(err);
  256. }
  257. return resolve(data);
  258. });
  259. });
  260. };
  261. pageSchema.statics.hasPortalPage = function (path, user) {
  262. var self = this;
  263. return new Promise(function(resolve, reject) {
  264. self.findPage(path, user)
  265. .then(function(page) {
  266. resolve(page);
  267. }).catch(function(err) {
  268. resolve(null); // check only has portal page, through error
  269. });
  270. });
  271. };
  272. pageSchema.statics.getGrantLabels = function() {
  273. var grantLabels = {};
  274. grantLabels[GRANT_PUBLIC] = '公開';
  275. grantLabels[GRANT_RESTRICTED] = 'リンクを知っている人のみ';
  276. //grantLabels[GRANT_SPECIFIED] = '特定ユーザーのみ';
  277. grantLabels[GRANT_OWNER] = '自分のみ';
  278. return grantLabels;
  279. };
  280. pageSchema.statics.normalizePath = function(path) {
  281. if (!path.match(/^\//)) {
  282. path = '/' + path;
  283. }
  284. return path;
  285. };
  286. pageSchema.statics.getUserPagePath = function(user) {
  287. return '/user/' + user.username;
  288. };
  289. pageSchema.statics.getDeletedPageName = function(path) {
  290. if (path.match('\/')) {
  291. path = path.substr(1);
  292. }
  293. return '/trash/' + path;
  294. };
  295. pageSchema.statics.getRevertDeletedPageName = function(path) {
  296. return path.replace('\/trash', '');
  297. };
  298. pageSchema.statics.isDeletableName = function(path) {
  299. var notDeletable = [
  300. /^\/user\/[^\/]+$/, // user page
  301. ];
  302. for (var i = 0; i < notDeletable.length; i++) {
  303. var pattern = notDeletable[i];
  304. if (path.match(pattern)) {
  305. return false;
  306. }
  307. }
  308. return true;
  309. };
  310. pageSchema.statics.isCreatableName = function(name) {
  311. var forbiddenPages = [
  312. /\^|\$|\*|\+|\#/,
  313. /^\/_.*/, // /_api/* and so on
  314. /^\/\-\/.*/,
  315. /^\/_r\/.*/,
  316. /^\/user\/[^\/]+\/(bookmarks|comments|activities|pages|recent-create|recent-edit)/, // reserved
  317. /^https?:\/\/.+$/, // avoid miss in renaming
  318. /.+\/edit$/,
  319. /.+\.md$/,
  320. /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments)(\/.*|$)/,
  321. ];
  322. var isCreatable = true;
  323. forbiddenPages.forEach(function(page) {
  324. var pageNameReg = new RegExp(page);
  325. if (name.match(pageNameReg)) {
  326. isCreatable = false;
  327. return ;
  328. }
  329. });
  330. return isCreatable;
  331. };
  332. pageSchema.statics.updateRevision = function(pageId, revisionId, cb) {
  333. this.update({_id: pageId}, {revision: revisionId}, {}, function(err, data) {
  334. cb(err, data);
  335. });
  336. };
  337. pageSchema.statics.findUpdatedList = function(offset, limit, cb) {
  338. this
  339. .find({})
  340. .sort({updatedAt: -1})
  341. .skip(offset)
  342. .limit(limit)
  343. .exec(function(err, data) {
  344. cb(err, data);
  345. });
  346. };
  347. pageSchema.statics.findPageById = function(id) {
  348. var Page = this;
  349. return new Promise(function(resolve, reject) {
  350. Page.findOne({_id: id}, function(err, pageData) {
  351. if (err) {
  352. return reject(err);
  353. }
  354. if (pageData == null) {
  355. return reject(new Error('Page not found'));
  356. }
  357. return Page.populatePageData(pageData, null).then(resolve);
  358. });
  359. });
  360. };
  361. pageSchema.statics.findPageByIdAndGrantedUser = function(id, userData) {
  362. var Page = this;
  363. return new Promise(function(resolve, reject) {
  364. Page.findPageById(id)
  365. .then(function(pageData) {
  366. if (userData && !pageData.isGrantedFor(userData)) {
  367. return reject(new Error('Page is not granted for the user')); //PAGE_GRANT_ERROR, null);
  368. }
  369. return resolve(pageData);
  370. }).catch(function(err) {
  371. return reject(err);
  372. });
  373. });
  374. };
  375. // find page and check if granted user
  376. pageSchema.statics.findPage = function(path, userData, revisionId, ignoreNotFound) {
  377. var self = this;
  378. return new Promise(function(resolve, reject) {
  379. self.findOne({path: path}, function(err, pageData) {
  380. if (err) {
  381. return reject(err);
  382. }
  383. if (pageData === null) {
  384. if (ignoreNotFound) {
  385. return resolve(null);
  386. }
  387. var pageNotFoundError = new Error('Page Not Found')
  388. pageNotFoundError.name = 'Crowi:Page:NotFound';
  389. return reject(pageNotFoundError);
  390. }
  391. if (!pageData.isGrantedFor(userData)) {
  392. return reject(new Error('Page is not granted for the user')); //PAGE_GRANT_ERROR, null);
  393. }
  394. self.populatePageData(pageData, revisionId || null).then(resolve).catch(reject);
  395. });
  396. });
  397. };
  398. // find page by path
  399. pageSchema.statics.findPageByPath = function(path) {
  400. var Page = this;
  401. return new Promise(function(resolve, reject) {
  402. Page.findOne({path: path}, function(err, pageData) {
  403. if (err || pageData === null) {
  404. return reject(err);
  405. }
  406. return resolve(pageData);
  407. });
  408. });
  409. };
  410. pageSchema.statics.findListByPageIds = function(ids, options) {
  411. var Page = this;
  412. var User = crowi.model('User');
  413. var options = options || {}
  414. , limit = options.limit || 50
  415. , offset = options.skip || 0
  416. ;
  417. return new Promise(function(resolve, reject) {
  418. Page
  419. .find({ _id: { $in: ids }, grant: GRANT_PUBLIC })
  420. //.sort({createdAt: -1}) // TODO optionize
  421. .skip(offset)
  422. .limit(limit)
  423. .populate([
  424. {path: 'creator', model: 'User', select: User.USER_PUBLIC_FIELDS},
  425. {path: 'revision', model: 'Revision'},
  426. ])
  427. .exec(function(err, pages) {
  428. if (err) {
  429. return reject(err);
  430. }
  431. Page.populate(pages, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS}, function(err, data) {
  432. if (err) {
  433. return reject(err);
  434. }
  435. return resolve(data);
  436. });
  437. });
  438. });
  439. };
  440. /**
  441. * とりあえず、公開ページであり、redirectTo が無いものだけを出すためだけのAPI
  442. */
  443. pageSchema.statics.findListByCreator = function(user, option) {
  444. var Page = this;
  445. var User = crowi.model('User');
  446. var limit = option.limit || 50;
  447. var offset = option.offset || 0;
  448. return new Promise(function(resolve, reject) {
  449. Page
  450. .find({ creator: user._id, grant: GRANT_PUBLIC, redirectTo: null })
  451. .sort({createdAt: -1})
  452. .skip(offset)
  453. .limit(limit)
  454. .populate('revision')
  455. .exec()
  456. .then(function(pages) {
  457. return Page.populate(pages, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS}).then(resolve);
  458. });
  459. });
  460. };
  461. /**
  462. * Bulk get (for internal only)
  463. */
  464. pageSchema.statics.getStreamOfFindAll = function(options) {
  465. var Page = this
  466. , options = options || {}
  467. , publicOnly = options.publicOnly || true
  468. , criteria = {redirectTo: null,}
  469. ;
  470. if (publicOnly) {
  471. criteria.grant = GRANT_PUBLIC;
  472. }
  473. return this.find(criteria)
  474. .populate([
  475. {path: 'creator', model: 'User'},
  476. {path: 'revision', model: 'Revision'},
  477. ])
  478. .sort({updatedAt: -1})
  479. .stream();
  480. };
  481. /**
  482. * findListByStartWith
  483. *
  484. * If `path` has `/` at the end, returns '{path}/*' and '{path}' self.
  485. * If `path` doesn't have `/` at the end, returns '{path}*'
  486. * e.g.
  487. */
  488. pageSchema.statics.findListByStartWith = function(path, userData, option) {
  489. var Page = this;
  490. var User = crowi.model('User');
  491. var pathCondition = [];
  492. if (!option) {
  493. option = {sort: 'updatedAt', desc: -1, offset: 0, limit: 50};
  494. }
  495. var opt = {
  496. sort: option.sort || 'updatedAt',
  497. desc: option.desc || -1,
  498. offset: option.offset || 0,
  499. limit: option.limit || 50
  500. };
  501. var sortOpt = {};
  502. sortOpt[opt.sort] = opt.desc;
  503. var queryReg = new RegExp('^' + path);
  504. var sliceOption = option.revisionSlice || {$slice: 1};
  505. pathCondition.push({path: queryReg});
  506. if (path.match(/\/$/)) {
  507. debug('Page list by ending with /, so find also upper level page');
  508. pathCondition.push({path: path.substr(0, path.length -1)});
  509. }
  510. return new Promise(function(resolve, reject) {
  511. // FIXME: might be heavy
  512. var q = Page.find({
  513. redirectTo: null,
  514. $or: [
  515. {grant: null},
  516. {grant: GRANT_PUBLIC},
  517. {grant: GRANT_RESTRICTED, grantedUsers: userData._id},
  518. {grant: GRANT_SPECIFIED, grantedUsers: userData._id},
  519. {grant: GRANT_OWNER, grantedUsers: userData._id},
  520. ],})
  521. .populate('revision')
  522. .and({
  523. $or: pathCondition
  524. })
  525. .sort(sortOpt)
  526. .skip(opt.offset)
  527. .limit(opt.limit);
  528. q.exec()
  529. .then(function(pages) {
  530. Page.populate(pages, {path: 'revision.author', model: 'User', select: User.USER_PUBLIC_FIELDS})
  531. .then(resolve)
  532. .catch(reject);
  533. })
  534. });
  535. };
  536. pageSchema.statics.updatePageProperty = function(page, updateData) {
  537. var Page = this;
  538. return new Promise(function(resolve, reject) {
  539. // TODO foreach して save
  540. Page.update({_id: page._id}, {$set: updateData}, function(err, data) {
  541. if (err) {
  542. return reject(err);
  543. }
  544. return resolve(data);
  545. });
  546. });
  547. };
  548. pageSchema.statics.updateGrant = function(page, grant, userData) {
  549. var Page = this;
  550. return new Promise(function(resolve, reject) {
  551. page.grant = grant;
  552. if (grant == GRANT_PUBLIC) {
  553. page.grantedUsers = [];
  554. } else {
  555. page.grantedUsers = [];
  556. page.grantedUsers.push(userData._id);
  557. }
  558. page.save(function(err, data) {
  559. debug('Page.updateGrant, saved grantedUsers.', err, data);
  560. if (err) {
  561. return reject(err);
  562. }
  563. return resolve(data);
  564. });
  565. });
  566. };
  567. // Instance method でいいのでは
  568. pageSchema.statics.pushToGrantedUsers = function(page, userData) {
  569. return new Promise(function(resolve, reject) {
  570. if (!page.grantedUsers || !Array.isArray(page.grantedUsers)) {
  571. page.grantedUsers = [];
  572. }
  573. page.grantedUsers.push(userData);
  574. page.save(function(err, data) {
  575. if (err) {
  576. return reject(err);
  577. }
  578. return resolve(data);
  579. });
  580. });
  581. };
  582. pageSchema.statics.pushRevision = function(pageData, newRevision, user) {
  583. var isCreate = false;
  584. if (pageData.revision === undefined) {
  585. debug('pushRevision on Create');
  586. isCreate = true;
  587. }
  588. return new Promise(function(resolve, reject) {
  589. newRevision.save(function(err, newRevision) {
  590. if (err) {
  591. debug('Error on saving revision', err);
  592. return reject(err);
  593. }
  594. debug('Successfully saved new revision', newRevision);
  595. pageData.revision = newRevision;
  596. pageData.updatedAt = Date.now();
  597. pageData.save(function(err, data) {
  598. if (err) {
  599. // todo: remove new revision?
  600. debug('Error on save page data (after push revision)', err);
  601. return reject(err);
  602. }
  603. resolve(data);
  604. if (!isCreate) {
  605. debug('pushRevision on Update');
  606. }
  607. });
  608. });
  609. });
  610. };
  611. pageSchema.statics.create = function(path, body, user, options) {
  612. var Page = this
  613. , Revision = crowi.model('Revision')
  614. , format = options.format || 'markdown'
  615. , grant = options.grant || GRANT_PUBLIC
  616. , redirectTo = options.redirectTo || null;
  617. // force public
  618. if (isPortalPath(path)) {
  619. grant = GRANT_PUBLIC;
  620. }
  621. return new Promise(function(resolve, reject) {
  622. Page.findOne({path: path}, function(err, pageData) {
  623. if (pageData) {
  624. return reject(new Error('Cannot create new page to existed path'));
  625. }
  626. var newPage = new Page();
  627. newPage.path = path;
  628. newPage.creator = user;
  629. newPage.createdAt = Date.now();
  630. newPage.updatedAt = Date.now();
  631. newPage.redirectTo = redirectTo;
  632. newPage.grant = grant;
  633. newPage.status = STATUS_PUBLISHED;
  634. newPage.grantedUsers = [];
  635. newPage.grantedUsers.push(user);
  636. newPage.save(function (err, newPage) {
  637. if (err) {
  638. return reject(err);
  639. }
  640. var newRevision = Revision.prepareRevision(newPage, body, user, {format: format});
  641. Page.pushRevision(newPage, newRevision, user).then(function(data) {
  642. resolve(data);
  643. pageEvent.emit('create', data, user);
  644. }).catch(function(err) {
  645. debug('Push Revision Error on create page', err);
  646. return reject(err);
  647. });
  648. });
  649. });
  650. });
  651. };
  652. pageSchema.statics.updatePage = function(pageData, body, user, options) {
  653. var Page = this
  654. , Revision = crowi.model('Revision')
  655. , grant = options.grant || null
  656. ;
  657. // update existing page
  658. var newRevision = Revision.prepareRevision(pageData, body, user);
  659. return new Promise(function(resolve, reject) {
  660. Page.pushRevision(pageData, newRevision, user)
  661. .then(function(revision) {
  662. if (grant != pageData.grant) {
  663. return Page.updateGrant(pageData, grant, user).then(function(data) {
  664. debug('Page grant update:', data);
  665. resolve(data);
  666. pageEvent.emit('update', data, user);
  667. });
  668. } else {
  669. resolve(pageData);
  670. pageEvent.emit('update', pageData, user);
  671. }
  672. }).catch(function(err) {
  673. debug('Error on update', err);
  674. debug('Error on update', err.stack);
  675. });
  676. });
  677. };
  678. pageSchema.statics.deletePage = function(pageData, user, options) {
  679. var Page = this
  680. , newPath = Page.getDeletedPageName(pageData.path)
  681. ;
  682. if (Page.isDeletableName(pageData.path)) {
  683. return new Promise(function(resolve, reject) {
  684. Page.updatePageProperty(pageData, {status: STATUS_DELEDED})
  685. .then(function(pageData) {
  686. return Page.rename(pageData, newPath, user, {createRedirectPage: true})
  687. }).then(function(pageData) {
  688. resolve(pageData);
  689. }).catch(reject);
  690. });
  691. } else {
  692. return Promise.reject('Page is not deletable.');
  693. }
  694. };
  695. pageSchema.statics.revertDeletedPage = function(pageData, user, options) {
  696. };
  697. pageSchema.statics.rename = function(pageData, newPagePath, user, options) {
  698. var Page = this
  699. , Revision = crowi.model('Revision')
  700. , path = pageData.path
  701. , createRedirectPage = options.createRedirectPage || 0
  702. , moveUnderTrees = options.moveUnderTrees || 0;
  703. return new Promise(function(resolve, reject) {
  704. // pageData の path を変更
  705. Page.updatePageProperty(pageData, {updatedAt: Date.now(), path: newPagePath})
  706. .then(function(data) {
  707. debug('Before ', pageData);
  708. // reivisions の path を変更
  709. return Revision.updateRevisionListByPath(path, {path: newPagePath}, {})
  710. }).then(function(data) {
  711. debug('After ', pageData);
  712. pageData.path = newPagePath;
  713. if (createRedirectPage) {
  714. var body = 'redirect ' + newPagePath;
  715. Page.create(path, body, user, {redirectTo: newPagePath}).then(resolve).catch(reject);
  716. } else {
  717. resolve(data);
  718. }
  719. pageEvent.emit('update', pageData, user); // update as renamed page
  720. });
  721. });
  722. };
  723. pageSchema.statics.getHistories = function() {
  724. // TODO
  725. return;
  726. };
  727. pageSchema.statics.GRANT_PUBLIC = GRANT_PUBLIC;
  728. pageSchema.statics.GRANT_RESTRICTED = GRANT_RESTRICTED;
  729. pageSchema.statics.GRANT_SPECIFIED = GRANT_SPECIFIED;
  730. pageSchema.statics.GRANT_OWNER = GRANT_OWNER;
  731. pageSchema.statics.PAGE_GRANT_ERROR = PAGE_GRANT_ERROR;
  732. return mongoose.model('Page', pageSchema);
  733. };