page.js 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  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. if (groupRelations != null) {
  298. renderVars.userRelatedGroups = groupRelations.map(relation => relation.relatedGroup);
  299. }
  300. return PageGroupRelation.findByPage(renderVars.page);
  301. }).then((pageGroupRelation) => {
  302. if (pageGroupRelation != null) {
  303. renderVars.pageRelatedGroup = pageGroupRelation.relatedGroup;
  304. }
  305. return Promise.resolve();
  306. });
  307. }
  308. actions.deletedPageListShow = function(req, res) {
  309. var path = '/trash' + getPathFromRequest(req);
  310. var limit = 50;
  311. var offset = parseInt(req.query.offset) || 0;
  312. // index page
  313. var pagerOptions = {
  314. offset: offset,
  315. limit : limit
  316. };
  317. var queryOptions = {
  318. offset: offset,
  319. limit : limit + 1,
  320. includeDeletedPage: true,
  321. };
  322. var renderVars = {
  323. page: null,
  324. path: path,
  325. pages: [],
  326. };
  327. Page.findListWithDescendants(path, req.user, queryOptions)
  328. .then(function(pageList) {
  329. if (pageList.length > limit) {
  330. pageList.pop();
  331. }
  332. pagerOptions.length = pageList.length;
  333. renderVars.pager = generatePager(pagerOptions);
  334. renderVars.pages = pagePathUtil.encodePagesPath(pageList);
  335. res.render('customlayout-selector/page_list', renderVars);
  336. }).catch(function(err) {
  337. debug('Error on rendering deletedPageListShow', err);
  338. });
  339. };
  340. actions.search = function(req, res) {
  341. // spec: ?q=query&sort=sort_order&author=author_filter
  342. var query = req.query.q;
  343. var search = require('../util/search')(crowi);
  344. search.searchPageByKeyword(query)
  345. .then(function(pages) {
  346. debug('pages', pages);
  347. if (pages.hits.total <= 0) {
  348. return Promise.resolve([]);
  349. }
  350. var ids = pages.hits.hits.map(function(page) {
  351. return page._id;
  352. });
  353. return Page.findListByPageIds(ids);
  354. }).then(function(pages) {
  355. res.render('customlayout-selector/page_list', {
  356. path: '/',
  357. pages: pagePathUtil.encodePagesPath(pages),
  358. pager: generatePager({offset: 0, limit: 50})
  359. });
  360. }).catch(function(err) {
  361. debug('search error', err);
  362. });
  363. };
  364. function renderPage(pageData, req, res) {
  365. // create page
  366. if (!pageData) {
  367. var userRelatedGroups
  368. UserGroupRelation.findAllRelationForUser(req.user)
  369. .then((groupRelations) => {
  370. userRelatedGroups = groupRelations.map(relation => relation.relatedGroup);
  371. return Promise.resolve();
  372. }).then(() => {
  373. debug('not found page user group resolver : ', userRelatedGroups);
  374. return res.render('customlayout-selector/not_found', {
  375. author: {},
  376. page: false,
  377. userRelatedGroups: userRelatedGroups,
  378. });
  379. });
  380. }
  381. if (pageData.redirectTo) {
  382. return res.redirect(encodeURI(pageData.redirectTo + '?redirectFrom=' + pagePathUtil.encodePagePath(pageData.path)));
  383. }
  384. var renderVars = {
  385. path: pageData.path,
  386. page: pageData,
  387. revision: pageData.revision || {},
  388. author: pageData.revision.author || false,
  389. };
  390. var userPage = isUserPage(pageData.path);
  391. var userData = null;
  392. Revision.findRevisionList(pageData.path, {})
  393. .then(function(tree) {
  394. renderVars.tree = tree;
  395. return Promise.resolve();
  396. }).then(function() {
  397. if (userPage) {
  398. return User.findUserByUsername(User.getUsernameByPath(pageData.path))
  399. .then(function(data) {
  400. if (data === null) {
  401. throw new Error('The user not found.');
  402. }
  403. userData = data;
  404. renderVars.pageUser = userData;
  405. return Bookmark.findByUser(userData, {limit: 10, populatePage: true, requestUser: req.user});
  406. }).then(function(bookmarkList) {
  407. renderVars.bookmarkList = bookmarkList;
  408. return Page.findListByCreator(userData, {limit: 10}, req.user);
  409. }).then(function(createdList) {
  410. renderVars.createdList = createdList;
  411. return Promise.resolve();
  412. }).catch(function(err) {
  413. debug('Error on finding user related entities', err);
  414. // pass
  415. });
  416. } else {
  417. return Promise.resolve();
  418. }
  419. }).then(function() {
  420. return interceptorManager.process('beforeRenderPage', req, res, renderVars);
  421. }).then(function() {
  422. var defaultPageTeamplate = 'customlayout-selector/page';
  423. if (userData) {
  424. defaultPageTeamplate = 'customlayout-selector/user_page';
  425. }
  426. res.render(req.query.presentation ? 'page_presentation' : defaultPageTeamplate, renderVars);
  427. }).catch(function(err) {
  428. debug('Error: renderPage()', err);
  429. if (err) {
  430. res.redirect('/');
  431. }
  432. });
  433. }
  434. actions.pageShow = function(req, res) {
  435. var path = path || getPathFromRequest(req);
  436. var options = {};
  437. // FIXME: せっかく getPathFromRequest になってるのにここが生 params[0] だとダサイ
  438. var isMarkdown = req.params[0].match(/.+\.md$/) || false;
  439. res.locals.path = path;
  440. Page.findPage(path, req.user, req.query.revision)
  441. .then(function(page) {
  442. debug('Page found', page._id, page.path);
  443. if (isMarkdown) {
  444. res.set('Content-Type', 'text/plain');
  445. return res.send(page.revision.body);
  446. }
  447. return renderPage(page, req, res);
  448. }).catch(function(err) {
  449. const normalizedPath = Page.normalizePath(path);
  450. if (normalizedPath !== path) {
  451. return res.redirect(normalizedPath);
  452. }
  453. // pageShow は /* にマッチしてる最後の砦なので、creatableName でない routing は
  454. // これ以前に定義されているはずなので、こうしてしまって問題ない。
  455. if (!Page.isCreatableName(path)) {
  456. // 削除済みページの場合 /trash 以下に移動しているので creatableName になっていないので、表示を許可
  457. debug('Page is not creatable name.', path);
  458. res.redirect('/');
  459. return ;
  460. }
  461. if (req.query.revision) {
  462. return res.redirect(pagePathUtil.encodePagePath(path));
  463. }
  464. if (isMarkdown) {
  465. return res.redirect('/');
  466. }
  467. Page.hasPortalPage(path + '/', req.user)
  468. .then(function(page) {
  469. if (page) {
  470. return res.redirect(pagePathUtil.encodePagePath(path) + '/');
  471. } else {
  472. var fixed = Page.fixToCreatableName(path)
  473. if (fixed !== path) {
  474. debug('fixed page name', fixed)
  475. res.redirect(pagePathUtil.encodePagePath(fixed));
  476. return ;
  477. }
  478. // if guest user
  479. if (!req.user) {
  480. res.redirect('/');
  481. }
  482. // render editor
  483. debug('Catch pageShow', err);
  484. return renderPage(null, req, res);
  485. }
  486. }).catch(function(err) {
  487. debug('Error on rendering pageShow (redirect to portal)', err);
  488. });
  489. });
  490. };
  491. actions.pageEdit = function(req, res) {
  492. var pageForm = req.body.pageForm;
  493. var body = pageForm.body;
  494. var currentRevision = pageForm.currentRevision;
  495. var grant = pageForm.grant;
  496. var path = pageForm.path;
  497. var grantUserGroupId = pageForm.grantUserGroupId
  498. // TODO: make it pluggable
  499. var notify = pageForm.notify || {};
  500. debug('notify: ', notify);
  501. var redirectPath = pagePathUtil.encodePagePath(path);
  502. var pageData = {};
  503. var updateOrCreate;
  504. var previousRevision = false;
  505. // set to render
  506. res.locals.pageForm = pageForm;
  507. // 削除済みページはここで編集不可判定される
  508. if (!Page.isCreatableName(path)) {
  509. res.redirect(redirectPath);
  510. return ;
  511. }
  512. var ignoreNotFound = true;
  513. Page.findPage(path, req.user, null, ignoreNotFound)
  514. .then(function(data) {
  515. pageData = data;
  516. if (!req.form.isValid) {
  517. debug('Form data not valid');
  518. throw new Error('Form data not valid.');
  519. }
  520. if (data && !data.isUpdatable(currentRevision)) {
  521. debug('Conflict occured');
  522. req.form.errors.push('page_edit.notice.conflict');
  523. throw new Error('Conflict.');
  524. }
  525. if (data) {
  526. previousRevision = data.revision;
  527. return Page.updatePage(data, body, req.user, { grant: grant, grantUserGroupId: grantUserGroupId});
  528. } else {
  529. // new page
  530. updateOrCreate = 'create';
  531. return Page.create(path, body, req.user, { grant: grant, grantUserGroupId: grantUserGroupId});
  532. }
  533. }).then(function(data) {
  534. // data is a saved page data.
  535. pageData = data;
  536. if (!data) {
  537. throw new Error('Data not found');
  538. }
  539. // TODO: move to events
  540. if (notify.slack) {
  541. if (notify.slack.on && notify.slack.channel) {
  542. data.updateSlackChannel(notify.slack.channel).then(function(){}).catch(function(){});
  543. if (crowi.slack) {
  544. notify.slack.channel.split(',').map(function(chan) {
  545. crowi.slack.post(pageData, req.user, chan, updateOrCreate, previousRevision)
  546. .catch((err) => {
  547. debug(err);
  548. });
  549. });
  550. }
  551. }
  552. }
  553. return res.redirect(redirectPath);
  554. }).catch(function(err) {
  555. debug('Page create or edit error.', err);
  556. if (pageData && !req.form.isValid) {
  557. return renderPage(pageData, req, res);
  558. }
  559. return res.redirect(redirectPath);
  560. });
  561. };
  562. var api = actions.api = {};
  563. /**
  564. * redirector
  565. */
  566. api.redirector = function(req, res){
  567. var id = req.params.id;
  568. Page.findPageById(id)
  569. .then(function(pageData) {
  570. if (pageData.grant == Page.GRANT_RESTRICTED && !pageData.isGrantedFor(req.user)) {
  571. return Page.pushToGrantedUsers(pageData, req.user);
  572. }
  573. return Promise.resolve(pageData);
  574. }).then(function(page) {
  575. return res.redirect(pagePathUtil.encodePagePath(page.path));
  576. }).catch(function(err) {
  577. return res.redirect('/');
  578. });
  579. };
  580. /**
  581. * @api {get} /pages.list List pages by user
  582. * @apiName ListPage
  583. * @apiGroup Page
  584. *
  585. * @apiParam {String} path
  586. * @apiParam {String} user
  587. */
  588. api.list = function(req, res) {
  589. var username = req.query.user || null;
  590. var path = req.query.path || null;
  591. var limit = 50;
  592. var offset = parseInt(req.query.offset) || 0;
  593. var pagerOptions = { offset: offset, limit : limit };
  594. var queryOptions = { offset: offset, limit : limit + 1};
  595. // Accepts only one of these
  596. if (username === null && path === null) {
  597. return res.json(ApiResponse.error('Parameter user or path is required.'));
  598. }
  599. if (username !== null && path !== null) {
  600. return res.json(ApiResponse.error('Parameter user or path is required.'));
  601. }
  602. var pageFetcher;
  603. if (path === null) {
  604. pageFetcher = User.findUserByUsername(username)
  605. .then(function(user) {
  606. if (user === null) {
  607. throw new Error('The user not found.');
  608. }
  609. return Page.findListByCreator(user, queryOptions, req.user);
  610. });
  611. } else {
  612. pageFetcher = Page.findListByStartWith(path, req.user, queryOptions);
  613. }
  614. pageFetcher
  615. .then(function(pages) {
  616. if (pages.length > limit) {
  617. pages.pop();
  618. }
  619. pagerOptions.length = pages.length;
  620. var result = {};
  621. result.pages = pagePathUtil.encodePagesPath(pages);
  622. return res.json(ApiResponse.success(result));
  623. }).catch(function(err) {
  624. return res.json(ApiResponse.error(err));
  625. });
  626. };
  627. /**
  628. * @api {post} /pages.create Create new page
  629. * @apiName CreatePage
  630. * @apiGroup Page
  631. *
  632. * @apiParam {String} body
  633. * @apiParam {String} path
  634. * @apiParam {String} grant
  635. */
  636. api.create = function(req, res){
  637. var body = req.body.body || null;
  638. var pagePath = req.body.path || null;
  639. var grant = req.body.grant || null;
  640. var grantUserGroupId = req.body.grantUserGroupId || null;
  641. if (body === null || pagePath === null) {
  642. return res.json(ApiResponse.error('Parameters body and path are required.'));
  643. }
  644. var ignoreNotFound = true;
  645. Page.findPage(pagePath, req.user, null, ignoreNotFound)
  646. .then(function(data) {
  647. if (data !== null) {
  648. throw new Error('Page exists');
  649. }
  650. return Page.create(pagePath, body, req.user, { grant: grant, grantUserGroupId: grantUserGroupId});
  651. }).then(function(data) {
  652. if (!data) {
  653. throw new Error('Failed to create page.');
  654. }
  655. var result = { page: data.toObject() };
  656. result.page.lastUpdateUser = User.filterToPublicFields(data.lastUpdateUser);
  657. result.page.creator = User.filterToPublicFields(data.creator);
  658. return res.json(ApiResponse.success(result));
  659. }).catch(function(err) {
  660. return res.json(ApiResponse.error(err));
  661. });;
  662. };
  663. /**
  664. * @api {post} /pages.update Update page
  665. * @apiName UpdatePage
  666. * @apiGroup Page
  667. *
  668. * @apiParam {String} body
  669. * @apiParam {String} page_id
  670. * @apiParam {String} revision_id
  671. * @apiParam {String} grant
  672. *
  673. * In the case of the page exists:
  674. * - If revision_id is specified => update the page,
  675. * - If revision_id is not specified => force update by the new contents.
  676. */
  677. api.update = function(req, res){
  678. var pageBody = req.body.body || null;
  679. var pageId = req.body.page_id || null;
  680. var revisionId = req.body.revision_id || null;
  681. var grant = req.body.grant || null;
  682. var grantUserGroupId = req.body.grantUserGroupId || null;
  683. if (pageId === null || pageBody === null) {
  684. return res.json(ApiResponse.error('page_id and body are required.'));
  685. }
  686. Page.findPageByIdAndGrantedUser(pageId, req.user)
  687. .then(function(pageData) {
  688. if (pageData && revisionId !== null && !pageData.isUpdatable(revisionId)) {
  689. throw new Error('Revision error.');
  690. };
  691. var grantOption = {grant: pageData.grant};
  692. if (grant !== null) {
  693. grantOption.grant = grant;
  694. }
  695. if (grantUserGroupId != null) {
  696. grantOption.grantUserGroupId = grantUserGroupId;
  697. }
  698. return Page.updatePage(pageData, pageBody, req.user, grantOption);
  699. }).then(function(pageData) {
  700. var result = {
  701. page: pageData.toObject(),
  702. };
  703. result.page.lastUpdateUser = User.filterToPublicFields(result.page.lastUpdateUser);
  704. return res.json(ApiResponse.success(result));
  705. }).catch(function(err) {
  706. debug('error on _api/pages.update', err);
  707. return res.json(ApiResponse.error(err));
  708. });
  709. };
  710. /**
  711. * @api {get} /pages.get Get page data
  712. * @apiName GetPage
  713. * @apiGroup Page
  714. *
  715. * @apiParam {String} page_id
  716. * @apiParam {String} path
  717. * @apiParam {String} revision_id
  718. */
  719. api.get = function(req, res){
  720. const pagePath = req.query.path || null;
  721. const pageId = req.query.page_id || null; // TODO: handling
  722. const revisionId = req.query.revision_id || null;
  723. if (!pageId && !pagePath) {
  724. return res.json(ApiResponse.error(new Error('Parameter path or page_id is required.')));
  725. }
  726. let pageFinder;
  727. if (pageId) { // prioritized
  728. pageFinder = Page.findPageByIdAndGrantedUser(pageId, req.user);
  729. } else if (pagePath) {
  730. pageFinder = Page.findPage(pagePath, req.user, revisionId);
  731. }
  732. pageFinder.then(function(pageData) {
  733. var result = {};
  734. result.page = pageData;
  735. return res.json(ApiResponse.success(result));
  736. }).catch(function(err) {
  737. return res.json(ApiResponse.error(err));
  738. });
  739. };
  740. /**
  741. * @api {post} /pages.seen Mark as seen user
  742. * @apiName SeenPage
  743. * @apiGroup Page
  744. *
  745. * @apiParam {String} page_id Page Id.
  746. */
  747. api.seen = function(req, res){
  748. var pageId = req.body.page_id;
  749. if (!pageId) {
  750. return res.json(ApiResponse.error('page_id required'));
  751. }
  752. Page.findPageByIdAndGrantedUser(pageId, req.user)
  753. .then(function(page) {
  754. return page.seen(req.user);
  755. }).then(function(user) {
  756. var result = {};
  757. result.seenUser = user;
  758. return res.json(ApiResponse.success(result));
  759. }).catch(function(err) {
  760. debug('Seen user update error', err);
  761. return res.json(ApiResponse.error(err));
  762. });
  763. };
  764. /**
  765. * @api {post} /likes.add Like page
  766. * @apiName LikePage
  767. * @apiGroup Page
  768. *
  769. * @apiParam {String} page_id Page Id.
  770. */
  771. api.like = function(req, res){
  772. var id = req.body.page_id;
  773. Page.findPageByIdAndGrantedUser(id, req.user)
  774. .then(function(pageData) {
  775. return pageData.like(req.user);
  776. }).then(function(data) {
  777. var result = {page: data};
  778. return res.json(ApiResponse.success(result));
  779. }).catch(function(err) {
  780. debug('Like failed', err);
  781. return res.json(ApiResponse.error({}));
  782. });
  783. };
  784. /**
  785. * @api {post} /likes.remove Unlike page
  786. * @apiName UnlikePage
  787. * @apiGroup Page
  788. *
  789. * @apiParam {String} page_id Page Id.
  790. */
  791. api.unlike = function(req, res){
  792. var id = req.body.page_id;
  793. Page.findPageByIdAndGrantedUser(id, req.user)
  794. .then(function(pageData) {
  795. return pageData.unlike(req.user);
  796. }).then(function(data) {
  797. var result = {page: data};
  798. return res.json(ApiResponse.success(result));
  799. }).catch(function(err) {
  800. debug('Unlike failed', err);
  801. return res.json(ApiResponse.error({}));
  802. });
  803. };
  804. /**
  805. * @api {get} /pages.updatePost
  806. * @apiName Get UpdatePost setting list
  807. * @apiGroup Page
  808. *
  809. * @apiParam {String} path
  810. */
  811. api.getUpdatePost = function(req, res) {
  812. var path = req.query.path;
  813. var UpdatePost = crowi.model('UpdatePost');
  814. if (!path) {
  815. return res.json(ApiResponse.error({}));
  816. }
  817. UpdatePost.findSettingsByPath(path)
  818. .then(function(data) {
  819. data = data.map(function(e) {
  820. return e.channel;
  821. });
  822. debug('Found updatePost data', data);
  823. var result = {updatePost: data};
  824. return res.json(ApiResponse.success(result));
  825. }).catch(function(err) {
  826. debug('Error occured while get setting', err);
  827. return res.json(ApiResponse.error({}));
  828. });
  829. };
  830. /**
  831. * @api {post} /pages.remove Remove page
  832. * @apiName RemovePage
  833. * @apiGroup Page
  834. *
  835. * @apiParam {String} page_id Page Id.
  836. * @apiParam {String} revision_id
  837. */
  838. api.remove = function(req, res){
  839. var pageId = req.body.page_id;
  840. var previousRevision = req.body.revision_id || null;
  841. // get completely flag
  842. const isCompletely = (req.body.completely !== undefined);
  843. // get recursively flag
  844. const isRecursively = (req.body.recursively !== undefined);
  845. Page.findPageByIdAndGrantedUser(pageId, req.user)
  846. .then(function(pageData) {
  847. debug('Delete page', pageData._id, pageData.path);
  848. if (isCompletely) {
  849. if (isRecursively) {
  850. return Page.completelyDeletePageRecursively(pageData, req.user);
  851. }
  852. else {
  853. return Page.completelyDeletePage(pageData, req.user);
  854. }
  855. }
  856. // else
  857. if (!pageData.isUpdatable(previousRevision)) {
  858. throw new Error('Someone could update this page, so couldn\'t delete.');
  859. }
  860. if (isRecursively) {
  861. return Page.deletePageRecursively(pageData, req.user);
  862. }
  863. else {
  864. return Page.deletePage(pageData, req.user);
  865. }
  866. }).then(function(data) {
  867. debug('Page deleted', data.path);
  868. var result = {};
  869. result.page = data;
  870. return res.json(ApiResponse.success(result));
  871. }).catch(function(err) {
  872. debug('Error occured while get setting', err, err.stack);
  873. return res.json(ApiResponse.error('Failed to delete page.'));
  874. });
  875. };
  876. /**
  877. * @api {post} /pages.revertRemove Revert removed page
  878. * @apiName RevertRemovePage
  879. * @apiGroup Page
  880. *
  881. * @apiParam {String} page_id Page Id.
  882. */
  883. api.revertRemove = function(req, res){
  884. var pageId = req.body.page_id;
  885. // get recursively flag
  886. const isRecursively = (req.body.recursively !== undefined);
  887. Page.findPageByIdAndGrantedUser(pageId, req.user)
  888. .then(function(pageData) {
  889. if (isRecursively) {
  890. return Page.revertDeletedPageRecursively(pageData, req.user);
  891. }
  892. else {
  893. return Page.revertDeletedPage(pageData, req.user);
  894. }
  895. }).then(function(data) {
  896. debug('Complete to revert deleted page', data.path);
  897. var result = {};
  898. result.page = data;
  899. return res.json(ApiResponse.success(result));
  900. }).catch(function(err) {
  901. debug('Error occured while get setting', err, err.stack);
  902. return res.json(ApiResponse.error('Failed to revert deleted page.'));
  903. });
  904. };
  905. /**
  906. * @api {post} /pages.rename Rename page
  907. * @apiName RenamePage
  908. * @apiGroup Page
  909. *
  910. * @apiParam {String} page_id Page Id.
  911. * @apiParam {String} path
  912. * @apiParam {String} revision_id
  913. * @apiParam {String} new_path
  914. * @apiParam {Bool} create_redirect
  915. */
  916. api.rename = function(req, res){
  917. var pageId = req.body.page_id;
  918. var previousRevision = req.body.revision_id || null;
  919. var newPagePath = Page.normalizePath(req.body.new_path);
  920. var options = {
  921. createRedirectPage: req.body.create_redirect || 0,
  922. moveUnderTrees: req.body.move_trees || 0,
  923. };
  924. var isRecursiveMove = req.body.move_recursively || 0;
  925. var page = {};
  926. if (!Page.isCreatableName(newPagePath)) {
  927. return res.json(ApiResponse.error(`このページ名は作成できません (${newPagePath})`));
  928. }
  929. Page.findPageByPath(newPagePath)
  930. .then(function(page) {
  931. // if page found, cannot cannot rename to that path
  932. return res.json(ApiResponse.error(`このページ名は作成できません (${newPagePath})。ページが存在します。`));
  933. }).catch(function(err) {
  934. Page.findPageById(pageId)
  935. .then(function(pageData) {
  936. page = pageData;
  937. if (!pageData.isUpdatable(previousRevision)) {
  938. throw new Error('Someone could update this page, so couldn\'t delete.');
  939. }
  940. if (isRecursiveMove) {
  941. return Page.renameRecursively(pageData, newPagePath, req.user, options);
  942. }
  943. else {
  944. return Page.rename(pageData, newPagePath, req.user, options);
  945. }
  946. }).then(function() {
  947. var result = {};
  948. result.page = page;
  949. return res.json(ApiResponse.success(result));
  950. }).catch(function(err) {
  951. return res.json(ApiResponse.error('Failed to update page.'));
  952. });
  953. });
  954. };
  955. /**
  956. * @api {post} /pages.duplicate Duplicate page
  957. * @apiName DuplicatePage
  958. * @apiGroup Page
  959. *
  960. * @apiParam {String} page_id Page Id.
  961. * @apiParam {String} new_path
  962. */
  963. api.duplicate = function (req, res) {
  964. var pageId = req.body.page_id;
  965. var newPagePath = Page.normalizePath(req.body.new_path);
  966. var page = {};
  967. Page.findPageById(pageId)
  968. .then(function (pageData) {
  969. req.body.path = newPagePath;
  970. req.body.body = pageData.revision.body;
  971. req.body.grant = pageData.grant;
  972. return api.create(req, res);
  973. });
  974. };
  975. /**
  976. * @api {post} /pages.unlink Remove the redirecting page
  977. * @apiName UnlinkPage
  978. * @apiGroup Page
  979. *
  980. * @apiParam {String} page_id Page Id.
  981. * @apiParam {String} revision_id
  982. */
  983. api.unlink = function(req, res){
  984. var pageId = req.body.page_id;
  985. Page.findPageByIdAndGrantedUser(pageId, req.user)
  986. .then(function(pageData) {
  987. debug('Unlink page', pageData._id, pageData.path);
  988. return Page.removeRedirectOriginPageByPath(pageData.path)
  989. .then(() => pageData);
  990. }).then(function(data) {
  991. debug('Redirect Page deleted', data.path);
  992. var result = {};
  993. result.page = data;
  994. return res.json(ApiResponse.success(result));
  995. }).catch(function(err) {
  996. debug('Error occured while get setting', err, err.stack);
  997. return res.json(ApiResponse.error('Failed to delete redirect page.'));
  998. });
  999. };
  1000. return actions;
  1001. };