page.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  1. module.exports = function(crowi, app) {
  2. 'use strict';
  3. var debug = require('debug')('crowi:routes:page')
  4. , Page = crowi.model('Page')
  5. , User = crowi.model('User')
  6. , Config = crowi.model('Config')
  7. , config = crowi.getConfig()
  8. , Revision = crowi.model('Revision')
  9. , Bookmark = crowi.model('Bookmark')
  10. , UserGroupRelation = crowi.model('UserGroupRelation')
  11. , PageGroupRelation = crowi.model('PageGroupRelation')
  12. , ApiResponse = require('../util/apiResponse')
  13. , interceptorManager = crowi.getInterceptorManager()
  14. , pagePathUtil = require('../util/pagePathUtil')
  15. , actions = {};
  16. // register page events
  17. var pageEvent = crowi.event('page');
  18. pageEvent.on('update', function(page, user) {
  19. crowi.getIo().sockets.emit('page edited', {page, user});
  20. });
  21. function getPathFromRequest(req) {
  22. var path = '/' + (req.params[0] || '');
  23. return path.replace(/\.md$/, '');
  24. }
  25. function isUserPage(path) {
  26. if (path.match(/^\/user\/[^\/]+\/?$/)) {
  27. return true;
  28. }
  29. return false;
  30. }
  31. // TODO: total とかでちゃんと計算する
  32. function generatePager(options) {
  33. var next = null,
  34. prev = null,
  35. offset = parseInt(options.offset, 10),
  36. limit = parseInt(options.limit, 10),
  37. length = options.length || 0;
  38. if (offset > 0) {
  39. prev = offset - limit;
  40. if (prev < 0) {
  41. prev = 0;
  42. }
  43. }
  44. if (length < limit) {
  45. next = null;
  46. } else {
  47. next = offset + limit;
  48. }
  49. return {
  50. prev: prev,
  51. next: next,
  52. offset: offset,
  53. };
  54. }
  55. /**
  56. * switch action by behaviorType
  57. */
  58. actions.pageListShowWrapper = function(req, res) {
  59. const behaviorType = Config.behaviorType(config);
  60. if (!behaviorType || 'crowi' === behaviorType) {
  61. return actions.pageListShow(req, res);
  62. }
  63. else {
  64. return actions.pageListShowForCrowiPlus(req, res);
  65. }
  66. }
  67. /**
  68. * switch action by behaviorType
  69. */
  70. actions.pageShowWrapper = function(req, res) {
  71. const behaviorType = Config.behaviorType(config);
  72. if (!behaviorType || 'crowi' === behaviorType) {
  73. return actions.pageShow(req, res);
  74. }
  75. else {
  76. return actions.pageShowForCrowiPlus(req, res);
  77. }
  78. }
  79. /**
  80. * switch action by behaviorType
  81. */
  82. actions.trashPageListShowWrapper = function(req, res) {
  83. const behaviorType = Config.behaviorType(config);
  84. if (!behaviorType || 'crowi' === behaviorType) {
  85. // Crowi behavior for '/trash/*'
  86. return actions.deletedPageListShow(req, res);
  87. }
  88. else {
  89. // redirect to '/trash'
  90. return res.redirect('/trash');
  91. }
  92. }
  93. /**
  94. * switch action by behaviorType
  95. */
  96. actions.trashPageShowWrapper = function(req, res) {
  97. const behaviorType = Config.behaviorType(config);
  98. if (!behaviorType || 'crowi' === behaviorType) {
  99. // redirect to '/trash/'
  100. return res.redirect('/trash/');
  101. }
  102. else {
  103. // Crowi behavior for '/trash/*'
  104. return actions.deletedPageListShow(req, res);
  105. }
  106. }
  107. /**
  108. * switch action by behaviorType
  109. */
  110. actions.deletedPageListShowWrapper = function(req, res) {
  111. const behaviorType = Config.behaviorType(config);
  112. if (!behaviorType || 'crowi' === behaviorType) {
  113. // Crowi behavior for '/trash/*'
  114. return actions.deletedPageListShow(req, res);
  115. }
  116. else {
  117. const path = '/trash' + getPathFromRequest(req);
  118. return res.redirect(path);
  119. }
  120. }
  121. actions.pageListShow = function(req, res) {
  122. var path = getPathFromRequest(req);
  123. var limit = 50;
  124. var offset = parseInt(req.query.offset) || 0;
  125. var SEENER_THRESHOLD = 10;
  126. // add slash if root
  127. path = path + (path == '/' ? '' : '/');
  128. debug('Page list show', path);
  129. // index page
  130. var pagerOptions = {
  131. offset: offset,
  132. limit : limit
  133. };
  134. var queryOptions = {
  135. offset: offset,
  136. limit : limit + 1,
  137. isPopulateRevisionBody: Config.isEnabledTimeline(config),
  138. };
  139. var renderVars = {
  140. page: null,
  141. path: path,
  142. isPortal: false,
  143. pages: [],
  144. tree: [],
  145. };
  146. Page.hasPortalPage(path, req.user, req.query.revision)
  147. .then(function(portalPage) {
  148. renderVars.page = portalPage;
  149. renderVars.isPortal = (portalPage != null);
  150. if (portalPage) {
  151. renderVars.revision = portalPage.revision;
  152. return Revision.findRevisionList(portalPage.path, {});
  153. } else {
  154. return Promise.resolve([]);
  155. }
  156. }).then(function(tree) {
  157. renderVars.tree = tree;
  158. return Page.findListByStartWith(path, req.user, queryOptions);
  159. }).then(function(pageList) {
  160. if (pageList.length > limit) {
  161. pageList.pop();
  162. }
  163. pagerOptions.length = pageList.length;
  164. renderVars.viewConfig = {
  165. seener_threshold: SEENER_THRESHOLD,
  166. };
  167. renderVars.pager = generatePager(pagerOptions);
  168. renderVars.pages = pagePathUtil.encodePagesPath(pageList);
  169. res.render('customlayout-selector/page_list', renderVars);
  170. }).catch(function(err) {
  171. debug('Error on rendering pageListShow', err);
  172. });
  173. };
  174. actions.pageListShowForCrowiPlus = function(req, res) {
  175. var path = getPathFromRequest(req);
  176. // omit the slash of the last
  177. path = path.replace((/\/$/), '');
  178. // redirect
  179. return res.redirect(path);
  180. }
  181. actions.pageShowForCrowiPlus = function(req, res) {
  182. var path = getPathFromRequest(req);
  183. var limit = 50;
  184. var offset = parseInt(req.query.offset) || 0;
  185. var SEENER_THRESHOLD = 10;
  186. // index page
  187. var pagerOptions = {
  188. offset: offset,
  189. limit : limit
  190. };
  191. var queryOptions = {
  192. offset: offset,
  193. limit : limit + 1,
  194. isPopulateRevisionBody: Config.isEnabledTimeline(config),
  195. includeDeletedPage: path.startsWith('/trash/'),
  196. };
  197. var renderVars = {
  198. path: path,
  199. page: null,
  200. revision: {},
  201. author: false,
  202. pages: [],
  203. tree: [],
  204. userRelatedGroups: [],
  205. pageRelatedGroup: null,
  206. };
  207. var pageTeamplate = 'customlayout-selector/page';
  208. var isRedirect = false;
  209. Page.findPage(path, req.user, req.query.revision)
  210. .then(function(page) {
  211. debug('Page found', page._id, page.path);
  212. // redirect
  213. if (page.redirectTo) {
  214. debug(`Redirect to '${page.redirectTo}'`);
  215. isRedirect = true;
  216. return res.redirect(encodeURI(page.redirectTo + '?redirectFrom=' + pagePathUtil.encodePagePath(page.path)));
  217. }
  218. renderVars.page = page;
  219. if (page) {
  220. renderVars.path = page.path;
  221. renderVars.revision = page.revision;
  222. renderVars.author = page.revision.author;
  223. return Revision.findRevisionList(page.path, {})
  224. .then(function(tree) {
  225. renderVars.tree = tree;
  226. return Promise.resolve();
  227. }).then(function() {
  228. var userPage = isUserPage(page.path);
  229. var userData = null;
  230. if (userPage) {
  231. // change template
  232. pageTeamplate = 'customlayout-selector/user_page';
  233. return User.findUserByUsername(User.getUsernameByPath(page.path))
  234. .then(function(data) {
  235. if (data === null) {
  236. throw new Error('The user not found.');
  237. }
  238. userData = data;
  239. renderVars.pageUser = userData;
  240. return Bookmark.findByUser(userData, {limit: 10, populatePage: true, requestUser: req.user});
  241. }).then(function(bookmarkList) {
  242. renderVars.bookmarkList = bookmarkList;
  243. return Page.findListByCreator(userData, {limit: 10}, req.user);
  244. }).then(function(createdList) {
  245. renderVars.createdList = createdList;
  246. return Promise.resolve();
  247. }).catch(function(err) {
  248. debug('Error on finding user related entities', err);
  249. // pass
  250. });
  251. }
  252. else {
  253. return Promise.resolve();
  254. }
  255. });
  256. } else {
  257. return Promise.resolve();
  258. }
  259. })
  260. // page not exists
  261. .catch(function(err) {
  262. debug('Page not found', path);
  263. // change template
  264. pageTeamplate = 'customlayout-selector/not_found';
  265. })
  266. // get list pages
  267. .then(function() {
  268. if (!isRedirect) {
  269. Page.findListWithDescendants(path, req.user, queryOptions)
  270. .then(function(pageList) {
  271. if (pageList.length > limit) {
  272. pageList.pop();
  273. }
  274. pagerOptions.length = pageList.length;
  275. renderVars.viewConfig = {
  276. seener_threshold: SEENER_THRESHOLD,
  277. };
  278. renderVars.pager = generatePager(pagerOptions);
  279. renderVars.pages = pagePathUtil.encodePagesPath(pageList);
  280. return Promise.resolve();
  281. })
  282. .then(function() {
  283. return interceptorManager.process('beforeRenderPage', req, res, renderVars);
  284. })
  285. .then(function() {
  286. res.render(req.query.presentation ? 'page_presentation' : pageTeamplate, renderVars);
  287. })
  288. .catch(function(err) {
  289. console.log(err);
  290. debug('Error on rendering pageListShowForCrowiPlus', err);
  291. });
  292. }
  293. })
  294. .then(function() {
  295. return UserGroupRelation.findAllRelationForUser(req.user);
  296. }).then(function (groupRelations) {
  297. renderVars.userRelatedGroups = groupRelations.map(relation => relation.relatedGroup);
  298. return PageGroupRelation.findByPage(renderVars.page);
  299. }).then((pageGroupRelation) => {
  300. renderVars.pageRelatedGroup = pageGroupRelation.relatedGroup;
  301. return Promise.resolve();
  302. });
  303. }
  304. actions.deletedPageListShow = function(req, res) {
  305. var path = '/trash' + getPathFromRequest(req);
  306. var limit = 50;
  307. var offset = parseInt(req.query.offset) || 0;
  308. // index page
  309. var pagerOptions = {
  310. offset: offset,
  311. limit : limit
  312. };
  313. var queryOptions = {
  314. offset: offset,
  315. limit : limit + 1,
  316. includeDeletedPage: true,
  317. };
  318. var renderVars = {
  319. page: null,
  320. path: path,
  321. pages: [],
  322. };
  323. Page.findListWithDescendants(path, req.user, queryOptions)
  324. .then(function(pageList) {
  325. if (pageList.length > limit) {
  326. pageList.pop();
  327. }
  328. pagerOptions.length = pageList.length;
  329. renderVars.pager = generatePager(pagerOptions);
  330. renderVars.pages = pagePathUtil.encodePagesPath(pageList);
  331. res.render('customlayout-selector/page_list', renderVars);
  332. }).catch(function(err) {
  333. debug('Error on rendering deletedPageListShow', err);
  334. });
  335. };
  336. actions.search = function(req, res) {
  337. // spec: ?q=query&sort=sort_order&author=author_filter
  338. var query = req.query.q;
  339. var search = require('../util/search')(crowi);
  340. search.searchPageByKeyword(query)
  341. .then(function(pages) {
  342. debug('pages', pages);
  343. if (pages.hits.total <= 0) {
  344. return Promise.resolve([]);
  345. }
  346. var ids = pages.hits.hits.map(function(page) {
  347. return page._id;
  348. });
  349. return Page.findListByPageIds(ids);
  350. }).then(function(pages) {
  351. res.render('customlayout-selector/page_list', {
  352. path: '/',
  353. pages: pagePathUtil.encodePagesPath(pages),
  354. pager: generatePager({offset: 0, limit: 50})
  355. });
  356. }).catch(function(err) {
  357. debug('search error', err);
  358. });
  359. };
  360. function renderPage(pageData, req, res) {
  361. // create page
  362. if (!pageData) {
  363. var userRelatedGroups
  364. UserGroupRelation.findAllRelationForUser(req.user)
  365. .then((groupRelations) => {
  366. userRelatedGroups = groupRelations.map(relation => relation.relatedGroup);
  367. return Promise.resolve();
  368. }).then(() => {
  369. debug('not found page user group resolver : ', userRelatedGroups);
  370. return res.render('customlayout-selector/not_found', {
  371. author: {},
  372. page: false,
  373. userRelatedGroups: userRelatedGroups,
  374. });
  375. });
  376. }
  377. if (pageData.redirectTo) {
  378. return res.redirect(encodeURI(pageData.redirectTo + '?redirectFrom=' + pagePathUtil.encodePagePath(pageData.path)));
  379. }
  380. var renderVars = {
  381. path: pageData.path,
  382. page: pageData,
  383. revision: pageData.revision || {},
  384. author: pageData.revision.author || false,
  385. };
  386. var userPage = isUserPage(pageData.path);
  387. var userData = null;
  388. Revision.findRevisionList(pageData.path, {})
  389. .then(function(tree) {
  390. renderVars.tree = tree;
  391. return Promise.resolve();
  392. }).then(function() {
  393. if (userPage) {
  394. return User.findUserByUsername(User.getUsernameByPath(pageData.path))
  395. .then(function(data) {
  396. if (data === null) {
  397. throw new Error('The user not found.');
  398. }
  399. userData = data;
  400. renderVars.pageUser = userData;
  401. return Bookmark.findByUser(userData, {limit: 10, populatePage: true, requestUser: req.user});
  402. }).then(function(bookmarkList) {
  403. renderVars.bookmarkList = bookmarkList;
  404. return Page.findListByCreator(userData, {limit: 10}, req.user);
  405. }).then(function(createdList) {
  406. renderVars.createdList = createdList;
  407. return Promise.resolve();
  408. }).catch(function(err) {
  409. debug('Error on finding user related entities', err);
  410. // pass
  411. });
  412. } else {
  413. return Promise.resolve();
  414. }
  415. }).then(function() {
  416. return interceptorManager.process('beforeRenderPage', req, res, renderVars);
  417. }).then(function() {
  418. var defaultPageTeamplate = 'customlayout-selector/page';
  419. if (userData) {
  420. defaultPageTeamplate = 'customlayout-selector/user_page';
  421. }
  422. res.render(req.query.presentation ? 'page_presentation' : defaultPageTeamplate, renderVars);
  423. }).catch(function(err) {
  424. debug('Error: renderPage()', err);
  425. if (err) {
  426. res.redirect('/');
  427. }
  428. });
  429. }
  430. actions.pageShow = function(req, res) {
  431. var path = path || getPathFromRequest(req);
  432. var options = {};
  433. // FIXME: せっかく getPathFromRequest になってるのにここが生 params[0] だとダサイ
  434. var isMarkdown = req.params[0].match(/.+\.md$/) || false;
  435. res.locals.path = path;
  436. Page.findPage(path, req.user, req.query.revision)
  437. .then(function(page) {
  438. debug('Page found', page._id, page.path);
  439. if (isMarkdown) {
  440. res.set('Content-Type', 'text/plain');
  441. return res.send(page.revision.body);
  442. }
  443. return renderPage(page, req, res);
  444. }).catch(function(err) {
  445. const normalizedPath = Page.normalizePath(path);
  446. if (normalizedPath !== path) {
  447. return res.redirect(normalizedPath);
  448. }
  449. // pageShow は /* にマッチしてる最後の砦なので、creatableName でない routing は
  450. // これ以前に定義されているはずなので、こうしてしまって問題ない。
  451. if (!Page.isCreatableName(path)) {
  452. // 削除済みページの場合 /trash 以下に移動しているので creatableName になっていないので、表示を許可
  453. debug('Page is not creatable name.', path);
  454. res.redirect('/');
  455. return ;
  456. }
  457. if (req.query.revision) {
  458. return res.redirect(pagePathUtil.encodePagePath(path));
  459. }
  460. if (isMarkdown) {
  461. return res.redirect('/');
  462. }
  463. Page.hasPortalPage(path + '/', req.user)
  464. .then(function(page) {
  465. if (page) {
  466. return res.redirect(pagePathUtil.encodePagePath(path) + '/');
  467. } else {
  468. var fixed = Page.fixToCreatableName(path)
  469. if (fixed !== path) {
  470. debug('fixed page name', fixed)
  471. res.redirect(pagePathUtil.encodePagePath(fixed));
  472. return ;
  473. }
  474. // if guest user
  475. if (!req.user) {
  476. res.redirect('/');
  477. }
  478. // render editor
  479. debug('Catch pageShow', err);
  480. return renderPage(null, req, res);
  481. }
  482. }).catch(function(err) {
  483. debug('Error on rendering pageShow (redirect to portal)', err);
  484. });
  485. });
  486. };
  487. actions.pageEdit = function(req, res) {
  488. var pageForm = req.body.pageForm;
  489. var body = pageForm.body;
  490. var currentRevision = pageForm.currentRevision;
  491. var grant = pageForm.grant;
  492. var path = pageForm.path;
  493. var grantUserGroupId = pageForm.grantUserGroupId
  494. // TODO: make it pluggable
  495. var notify = pageForm.notify || {};
  496. debug('notify: ', notify);
  497. var redirectPath = pagePathUtil.encodePagePath(path);
  498. var pageData = {};
  499. var updateOrCreate;
  500. var previousRevision = false;
  501. // set to render
  502. res.locals.pageForm = pageForm;
  503. // 削除済みページはここで編集不可判定される
  504. if (!Page.isCreatableName(path)) {
  505. res.redirect(redirectPath);
  506. return ;
  507. }
  508. var ignoreNotFound = true;
  509. Page.findPage(path, req.user, null, ignoreNotFound)
  510. .then(function(data) {
  511. pageData = data;
  512. if (!req.form.isValid) {
  513. debug('Form data not valid');
  514. throw new Error('Form data not valid.');
  515. }
  516. if (data && !data.isUpdatable(currentRevision)) {
  517. debug('Conflict occured');
  518. req.form.errors.push('page_edit.notice.conflict');
  519. throw new Error('Conflict.');
  520. }
  521. if (data) {
  522. previousRevision = data.revision;
  523. return Page.updatePage(data, body, req.user, { grant: grant, grantUserGroupId: grantUserGroupId});
  524. } else {
  525. // new page
  526. updateOrCreate = 'create';
  527. return Page.create(path, body, req.user, { grant: grant, grantUserGroupId: grantUserGroupId});
  528. }
  529. }).then(function(data) {
  530. // data is a saved page data.
  531. pageData = data;
  532. if (!data) {
  533. throw new Error('Data not found');
  534. }
  535. // TODO: move to events
  536. if (notify.slack) {
  537. if (notify.slack.on && notify.slack.channel) {
  538. data.updateSlackChannel(notify.slack.channel).then(function(){}).catch(function(){});
  539. if (crowi.slack) {
  540. notify.slack.channel.split(',').map(function(chan) {
  541. crowi.slack.post(pageData, req.user, chan, updateOrCreate, previousRevision)
  542. .catch((err) => {
  543. debug(err);
  544. });
  545. });
  546. }
  547. }
  548. }
  549. return res.redirect(redirectPath);
  550. }).catch(function(err) {
  551. debug('Page create or edit error.', err);
  552. if (pageData && !req.form.isValid) {
  553. return renderPage(pageData, req, res);
  554. }
  555. return res.redirect(redirectPath);
  556. });
  557. };
  558. var api = actions.api = {};
  559. /**
  560. * redirector
  561. */
  562. api.redirector = function(req, res){
  563. var id = req.params.id;
  564. Page.findPageById(id)
  565. .then(function(pageData) {
  566. if (pageData.grant == Page.GRANT_RESTRICTED && !pageData.isGrantedFor(req.user)) {
  567. return Page.pushToGrantedUsers(pageData, req.user);
  568. }
  569. return Promise.resolve(pageData);
  570. }).then(function(page) {
  571. return res.redirect(pagePathUtil.encodePagePath(page.path));
  572. }).catch(function(err) {
  573. return res.redirect('/');
  574. });
  575. };
  576. /**
  577. * @api {get} /pages.list List pages by user
  578. * @apiName ListPage
  579. * @apiGroup Page
  580. *
  581. * @apiParam {String} path
  582. * @apiParam {String} user
  583. */
  584. api.list = function(req, res) {
  585. var username = req.query.user || null;
  586. var path = req.query.path || null;
  587. var limit = 50;
  588. var offset = parseInt(req.query.offset) || 0;
  589. var pagerOptions = { offset: offset, limit : limit };
  590. var queryOptions = { offset: offset, limit : limit + 1};
  591. // Accepts only one of these
  592. if (username === null && path === null) {
  593. return res.json(ApiResponse.error('Parameter user or path is required.'));
  594. }
  595. if (username !== null && path !== null) {
  596. return res.json(ApiResponse.error('Parameter user or path is required.'));
  597. }
  598. var pageFetcher;
  599. if (path === null) {
  600. pageFetcher = User.findUserByUsername(username)
  601. .then(function(user) {
  602. if (user === null) {
  603. throw new Error('The user not found.');
  604. }
  605. return Page.findListByCreator(user, queryOptions, req.user);
  606. });
  607. } else {
  608. pageFetcher = Page.findListByStartWith(path, req.user, queryOptions);
  609. }
  610. pageFetcher
  611. .then(function(pages) {
  612. if (pages.length > limit) {
  613. pages.pop();
  614. }
  615. pagerOptions.length = pages.length;
  616. var result = {};
  617. result.pages = pagePathUtil.encodePagesPath(pages);
  618. return res.json(ApiResponse.success(result));
  619. }).catch(function(err) {
  620. return res.json(ApiResponse.error(err));
  621. });
  622. };
  623. /**
  624. * @api {post} /pages.create Create new page
  625. * @apiName CreatePage
  626. * @apiGroup Page
  627. *
  628. * @apiParam {String} body
  629. * @apiParam {String} path
  630. * @apiParam {String} grant
  631. */
  632. api.create = function(req, res){
  633. var body = req.body.body || null;
  634. var pagePath = req.body.path || null;
  635. var grant = req.body.grant || null;
  636. var grantUserGroupId = req.body.grantUserGroupId || null;
  637. if (body === null || pagePath === null) {
  638. return res.json(ApiResponse.error('Parameters body and path are required.'));
  639. }
  640. var ignoreNotFound = true;
  641. Page.findPage(pagePath, req.user, null, ignoreNotFound)
  642. .then(function(data) {
  643. if (data !== null) {
  644. throw new Error('Page exists');
  645. }
  646. return Page.create(pagePath, body, req.user, { grant: grant, grantUserGroupId: grantUserGroupId});
  647. }).then(function(data) {
  648. if (!data) {
  649. throw new Error('Failed to create page.');
  650. }
  651. var result = { page: data.toObject() };
  652. result.page.lastUpdateUser = User.filterToPublicFields(data.lastUpdateUser);
  653. result.page.creator = User.filterToPublicFields(data.creator);
  654. return res.json(ApiResponse.success(result));
  655. }).catch(function(err) {
  656. return res.json(ApiResponse.error(err));
  657. });;
  658. };
  659. /**
  660. * @api {post} /pages.update Update page
  661. * @apiName UpdatePage
  662. * @apiGroup Page
  663. *
  664. * @apiParam {String} body
  665. * @apiParam {String} page_id
  666. * @apiParam {String} revision_id
  667. * @apiParam {String} grant
  668. *
  669. * In the case of the page exists:
  670. * - If revision_id is specified => update the page,
  671. * - If revision_id is not specified => force update by the new contents.
  672. */
  673. api.update = function(req, res){
  674. var pageBody = req.body.body || null;
  675. var pageId = req.body.page_id || null;
  676. var revisionId = req.body.revision_id || null;
  677. var grant = req.body.grant || null;
  678. var grantUserGroupId = req.body.grantUserGroupId || null;
  679. if (pageId === null || pageBody === null) {
  680. return res.json(ApiResponse.error('page_id and body are required.'));
  681. }
  682. Page.findPageByIdAndGrantedUser(pageId, req.user)
  683. .then(function(pageData) {
  684. if (pageData && revisionId !== null && !pageData.isUpdatable(revisionId)) {
  685. throw new Error('Revision error.');
  686. };
  687. var grantOption = {grant: pageData.grant};
  688. if (grant !== null) {
  689. grantOption.grant = grant;
  690. }
  691. if (grantUserGroupId != null) {
  692. grantOption.grantUserGroupId = grantUserGroupId;
  693. }
  694. return Page.updatePage(pageData, pageBody, req.user, grantOption);
  695. }).then(function(pageData) {
  696. var result = {
  697. page: pageData.toObject(),
  698. };
  699. result.page.lastUpdateUser = User.filterToPublicFields(result.page.lastUpdateUser);
  700. return res.json(ApiResponse.success(result));
  701. }).catch(function(err) {
  702. debug('error on _api/pages.update', err);
  703. return res.json(ApiResponse.error(err));
  704. });
  705. };
  706. /**
  707. * @api {get} /pages.get Get page data
  708. * @apiName GetPage
  709. * @apiGroup Page
  710. *
  711. * @apiParam {String} page_id
  712. * @apiParam {String} path
  713. * @apiParam {String} revision_id
  714. */
  715. api.get = function(req, res){
  716. const pagePath = req.query.path || null;
  717. const pageId = req.query.page_id || null; // TODO: handling
  718. const revisionId = req.query.revision_id || null;
  719. if (!pageId && !pagePath) {
  720. return res.json(ApiResponse.error(new Error('Parameter path or page_id is required.')));
  721. }
  722. let pageFinder;
  723. if (pageId) { // prioritized
  724. pageFinder = Page.findPageByIdAndGrantedUser(pageId, req.user);
  725. } else if (pagePath) {
  726. pageFinder = Page.findPage(pagePath, req.user, revisionId);
  727. }
  728. pageFinder.then(function(pageData) {
  729. var result = {};
  730. result.page = pageData;
  731. return res.json(ApiResponse.success(result));
  732. }).catch(function(err) {
  733. return res.json(ApiResponse.error(err));
  734. });
  735. };
  736. /**
  737. * @api {post} /pages.seen Mark as seen user
  738. * @apiName SeenPage
  739. * @apiGroup Page
  740. *
  741. * @apiParam {String} page_id Page Id.
  742. */
  743. api.seen = function(req, res){
  744. var pageId = req.body.page_id;
  745. if (!pageId) {
  746. return res.json(ApiResponse.error('page_id required'));
  747. }
  748. Page.findPageByIdAndGrantedUser(pageId, req.user)
  749. .then(function(page) {
  750. return page.seen(req.user);
  751. }).then(function(user) {
  752. var result = {};
  753. result.seenUser = user;
  754. return res.json(ApiResponse.success(result));
  755. }).catch(function(err) {
  756. debug('Seen user update error', err);
  757. return res.json(ApiResponse.error(err));
  758. });
  759. };
  760. /**
  761. * @api {post} /likes.add Like page
  762. * @apiName LikePage
  763. * @apiGroup Page
  764. *
  765. * @apiParam {String} page_id Page Id.
  766. */
  767. api.like = function(req, res){
  768. var id = req.body.page_id;
  769. Page.findPageByIdAndGrantedUser(id, req.user)
  770. .then(function(pageData) {
  771. return pageData.like(req.user);
  772. }).then(function(data) {
  773. var result = {page: data};
  774. return res.json(ApiResponse.success(result));
  775. }).catch(function(err) {
  776. debug('Like failed', err);
  777. return res.json(ApiResponse.error({}));
  778. });
  779. };
  780. /**
  781. * @api {post} /likes.remove Unlike page
  782. * @apiName UnlikePage
  783. * @apiGroup Page
  784. *
  785. * @apiParam {String} page_id Page Id.
  786. */
  787. api.unlike = function(req, res){
  788. var id = req.body.page_id;
  789. Page.findPageByIdAndGrantedUser(id, req.user)
  790. .then(function(pageData) {
  791. return pageData.unlike(req.user);
  792. }).then(function(data) {
  793. var result = {page: data};
  794. return res.json(ApiResponse.success(result));
  795. }).catch(function(err) {
  796. debug('Unlike failed', err);
  797. return res.json(ApiResponse.error({}));
  798. });
  799. };
  800. /**
  801. * @api {get} /pages.updatePost
  802. * @apiName Get UpdatePost setting list
  803. * @apiGroup Page
  804. *
  805. * @apiParam {String} path
  806. */
  807. api.getUpdatePost = function(req, res) {
  808. var path = req.query.path;
  809. var UpdatePost = crowi.model('UpdatePost');
  810. if (!path) {
  811. return res.json(ApiResponse.error({}));
  812. }
  813. UpdatePost.findSettingsByPath(path)
  814. .then(function(data) {
  815. data = data.map(function(e) {
  816. return e.channel;
  817. });
  818. debug('Found updatePost data', data);
  819. var result = {updatePost: data};
  820. return res.json(ApiResponse.success(result));
  821. }).catch(function(err) {
  822. debug('Error occured while get setting', err);
  823. return res.json(ApiResponse.error({}));
  824. });
  825. };
  826. /**
  827. * @api {post} /pages.remove Remove page
  828. * @apiName RemovePage
  829. * @apiGroup Page
  830. *
  831. * @apiParam {String} page_id Page Id.
  832. * @apiParam {String} revision_id
  833. */
  834. api.remove = function(req, res){
  835. var pageId = req.body.page_id;
  836. var previousRevision = req.body.revision_id || null;
  837. // get completely flag
  838. const isCompletely = (req.body.completely !== undefined);
  839. // get recursively flag
  840. const isRecursively = (req.body.recursively !== undefined);
  841. Page.findPageByIdAndGrantedUser(pageId, req.user)
  842. .then(function(pageData) {
  843. debug('Delete page', pageData._id, pageData.path);
  844. if (isCompletely) {
  845. if (isRecursively) {
  846. return Page.completelyDeletePageRecursively(pageData, req.user);
  847. }
  848. else {
  849. return Page.completelyDeletePage(pageData, req.user);
  850. }
  851. }
  852. // else
  853. if (!pageData.isUpdatable(previousRevision)) {
  854. throw new Error('Someone could update this page, so couldn\'t delete.');
  855. }
  856. if (isRecursively) {
  857. return Page.deletePageRecursively(pageData, req.user);
  858. }
  859. else {
  860. return Page.deletePage(pageData, req.user);
  861. }
  862. }).then(function(data) {
  863. debug('Page deleted', data.path);
  864. var result = {};
  865. result.page = data;
  866. return res.json(ApiResponse.success(result));
  867. }).catch(function(err) {
  868. debug('Error occured while get setting', err, err.stack);
  869. return res.json(ApiResponse.error('Failed to delete page.'));
  870. });
  871. };
  872. /**
  873. * @api {post} /pages.revertRemove Revert removed page
  874. * @apiName RevertRemovePage
  875. * @apiGroup Page
  876. *
  877. * @apiParam {String} page_id Page Id.
  878. */
  879. api.revertRemove = function(req, res){
  880. var pageId = req.body.page_id;
  881. // get recursively flag
  882. const isRecursively = (req.body.recursively !== undefined);
  883. Page.findPageByIdAndGrantedUser(pageId, req.user)
  884. .then(function(pageData) {
  885. if (isRecursively) {
  886. return Page.revertDeletedPageRecursively(pageData, req.user);
  887. }
  888. else {
  889. return Page.revertDeletedPage(pageData, req.user);
  890. }
  891. }).then(function(data) {
  892. debug('Complete to revert deleted page', data.path);
  893. var result = {};
  894. result.page = data;
  895. return res.json(ApiResponse.success(result));
  896. }).catch(function(err) {
  897. debug('Error occured while get setting', err, err.stack);
  898. return res.json(ApiResponse.error('Failed to revert deleted page.'));
  899. });
  900. };
  901. /**
  902. * @api {post} /pages.rename Rename page
  903. * @apiName RenamePage
  904. * @apiGroup Page
  905. *
  906. * @apiParam {String} page_id Page Id.
  907. * @apiParam {String} path
  908. * @apiParam {String} revision_id
  909. * @apiParam {String} new_path
  910. * @apiParam {Bool} create_redirect
  911. */
  912. api.rename = function(req, res){
  913. var pageId = req.body.page_id;
  914. var previousRevision = req.body.revision_id || null;
  915. var newPagePath = Page.normalizePath(req.body.new_path);
  916. var options = {
  917. createRedirectPage: req.body.create_redirect || 0,
  918. moveUnderTrees: req.body.move_trees || 0,
  919. };
  920. var isRecursiveMove = req.body.move_recursively || 0;
  921. var page = {};
  922. if (!Page.isCreatableName(newPagePath)) {
  923. return res.json(ApiResponse.error(`このページ名は作成できません (${newPagePath})`));
  924. }
  925. Page.findPageByPath(newPagePath)
  926. .then(function(page) {
  927. // if page found, cannot cannot rename to that path
  928. return res.json(ApiResponse.error(`このページ名は作成できません (${newPagePath})。ページが存在します。`));
  929. }).catch(function(err) {
  930. Page.findPageById(pageId)
  931. .then(function(pageData) {
  932. page = pageData;
  933. if (!pageData.isUpdatable(previousRevision)) {
  934. throw new Error('Someone could update this page, so couldn\'t delete.');
  935. }
  936. if (isRecursiveMove) {
  937. return Page.renameRecursively(pageData, newPagePath, req.user, options);
  938. }
  939. else {
  940. return Page.rename(pageData, newPagePath, req.user, options);
  941. }
  942. }).then(function() {
  943. var result = {};
  944. result.page = page;
  945. return res.json(ApiResponse.success(result));
  946. }).catch(function(err) {
  947. return res.json(ApiResponse.error('Failed to update page.'));
  948. });
  949. });
  950. };
  951. /**
  952. * @api {post} /pages.duplicate Duplicate page
  953. * @apiName DuplicatePage
  954. * @apiGroup Page
  955. *
  956. * @apiParam {String} page_id Page Id.
  957. * @apiParam {String} new_path
  958. */
  959. api.duplicate = function (req, res) {
  960. var pageId = req.body.page_id;
  961. var newPagePath = Page.normalizePath(req.body.new_path);
  962. var page = {};
  963. Page.findPageById(pageId)
  964. .then(function (pageData) {
  965. req.body.path = newPagePath;
  966. req.body.body = pageData.revision.body;
  967. req.body.grant = pageData.grant;
  968. return api.create(req, res);
  969. });
  970. };
  971. /**
  972. * @api {post} /pages.unlink Remove the redirecting page
  973. * @apiName UnlinkPage
  974. * @apiGroup Page
  975. *
  976. * @apiParam {String} page_id Page Id.
  977. * @apiParam {String} revision_id
  978. */
  979. api.unlink = function(req, res){
  980. var pageId = req.body.page_id;
  981. Page.findPageByIdAndGrantedUser(pageId, req.user)
  982. .then(function(pageData) {
  983. debug('Unlink page', pageData._id, pageData.path);
  984. return Page.removeRedirectOriginPageByPath(pageData.path)
  985. .then(() => pageData);
  986. }).then(function(data) {
  987. debug('Redirect Page deleted', data.path);
  988. var result = {};
  989. result.page = data;
  990. return res.json(ApiResponse.success(result));
  991. }).catch(function(err) {
  992. debug('Error occured while get setting', err, err.stack);
  993. return res.json(ApiResponse.error('Failed to delete redirect page.'));
  994. });
  995. };
  996. return actions;
  997. };