page.js 33 KB

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