ogp.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import * as fs from 'fs';
  2. import path from 'path';
  3. import { DevidedPagePath } from '@growi/core';
  4. // eslint-disable-next-line no-restricted-imports
  5. import axios from 'axios';
  6. import {
  7. Request, Response, NextFunction,
  8. } from 'express';
  9. import { param, validationResult, ValidationError } from 'express-validator';
  10. import loggerFactory from '~/utils/logger';
  11. import { projectRoot } from '~/utils/project-dir-utils';
  12. import { convertStreamToBuffer } from '../util/stream';
  13. const logger = loggerFactory('growi:routes:ogp');
  14. const DEFAULT_USER_IMAGE_URL = '/images/icons/user.svg';
  15. const DEFAULT_USER_IMAGE_PATH = `public${DEFAULT_USER_IMAGE_URL}`;
  16. let bufferedDefaultUserImageCache: Buffer = Buffer.from('');
  17. fs.readFile(path.join(projectRoot, DEFAULT_USER_IMAGE_PATH), (err, buffer) => {
  18. if (err) throw err;
  19. bufferedDefaultUserImageCache = buffer;
  20. });
  21. module.exports = function(crowi) {
  22. const isUserImageAttachment = (userImageUrlCached: string): boolean => {
  23. return /^\/attachment\/.+/.test(userImageUrlCached);
  24. };
  25. const getBufferedUserImage = async(userImageUrlCached: string): Promise<Buffer> => {
  26. let bufferedUserImage: Buffer;
  27. if (isUserImageAttachment(userImageUrlCached)) {
  28. const { fileUploadService } = crowi;
  29. const Attachment = crowi.model('Attachment');
  30. const attachment = await Attachment.findById(userImageUrlCached);
  31. const fileStream = await fileUploadService.findDeliveryFile(attachment);
  32. bufferedUserImage = await convertStreamToBuffer(fileStream);
  33. return bufferedUserImage;
  34. }
  35. return (await axios.get(
  36. userImageUrlCached, {
  37. responseType: 'arraybuffer',
  38. },
  39. )).data;
  40. };
  41. const renderOgp = async(req: Request, res: Response) => {
  42. const { configManager } = crowi;
  43. const ogpUri = configManager.getConfig('crowi', 'app:ogpUri');
  44. const page = req.body.page;
  45. let user;
  46. let pageTitle: string;
  47. let bufferedUserImage: Buffer;
  48. try {
  49. const User = crowi.model('User');
  50. user = await User.findById(page.creator._id.toString());
  51. bufferedUserImage = user.imageUrlCached === DEFAULT_USER_IMAGE_URL ? bufferedDefaultUserImageCache : (await getBufferedUserImage(user.imageUrlCached));
  52. // todo: consider page title
  53. pageTitle = (new DevidedPagePath(page.path)).latter;
  54. }
  55. catch (err) {
  56. logger.error(err);
  57. return res.status(500).send(`error: ${err}`);
  58. }
  59. let result;
  60. try {
  61. result = await axios.post(
  62. ogpUri, {
  63. data: {
  64. title: pageTitle,
  65. userName: user.username,
  66. userImage: bufferedUserImage,
  67. },
  68. }, {
  69. responseType: 'stream',
  70. },
  71. );
  72. }
  73. catch (err) {
  74. logger.error(err);
  75. return res.status(500).send(`error: ${err}`);
  76. }
  77. res.writeHead(200, {
  78. 'Content-Type': 'image/jpeg',
  79. });
  80. result.data.pipe(res);
  81. };
  82. const pageIdRequired = param('pageId').not().isEmpty().withMessage('page id is not included in the parameter');
  83. const ogpValidator = async(req:Request, res:Response, next:NextFunction) => {
  84. const { aclService, fileUploadService, configManager } = crowi;
  85. const ogpUri = configManager.getConfig('crowi', 'app:ogpUri');
  86. if (ogpUri == null) return res.status(400).send('OGP URI for GROWI has not been setup');
  87. if (!fileUploadService.getIsUploadable()) return res.status(501).send('This GROWI can not upload file');
  88. if (!aclService.isGuestAllowedToRead()) return res.status(501).send('This GROWI is not public');
  89. const errors = validationResult(req);
  90. if (errors.isEmpty()) {
  91. try {
  92. const Page = crowi.model('Page');
  93. const page = await Page.findByIdAndViewer(req.params.pageId);
  94. if (page == null || page.status !== Page.STATUS_PUBLISHED || (page.grant !== Page.GRANT_PUBLIC && page.grant !== Page.GRANT_RESTRICTED)) {
  95. return res.status(400).send('the page does not exist');
  96. }
  97. req.body.page = page;
  98. }
  99. catch (error) {
  100. logger.error(error);
  101. return res.status(500).send(`error: ${error}`);
  102. }
  103. return next();
  104. }
  105. // errors.array length is one bacause pageIdRequired is used
  106. const pageIdRequiredError: ValidationError = errors.array()[0];
  107. return res.status(400).send(pageIdRequiredError.msg);
  108. };
  109. return {
  110. renderOgp,
  111. pageIdRequired,
  112. ogpValidator,
  113. };
  114. };