فهرست منبع

refactor router

Yuki Takei 2 سال پیش
والد
کامیت
686b3b8223

+ 2 - 0
packages/remark-lsx/package.json

@@ -29,6 +29,8 @@
     "@growi/remark-growi-directive": "^6.1.3-RC.0",
     "@growi/ui": "^6.1.3-RC.0",
     "escape-string-regexp": "^4.0.0",
+    "express": "^4.16.1",
+    "mongoose": "^6.5.0",
     "swr": "^2.0.3"
   },
   "devDependencies": {

+ 5 - 5
packages/remark-lsx/src/server/index.ts

@@ -1,17 +1,17 @@
-import { routesFactory } from './routes/lsx';
+import type { Request, Response } from 'express';
 
-const loginRequiredFallback = (req, res) => {
+import { listPages } from './routes/lsx';
+
+const loginRequiredFallback = (req: Request, res: Response) => {
   return res.status(403).send('login required');
 };
 
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
 const middleware = (crowi: any, app: any): void => {
-  const lsx = routesFactory(crowi);
-
   const loginRequired = crowi.require('../middlewares/login-required')(crowi, true, loginRequiredFallback);
   const accessTokenParser = crowi.require('../middlewares/access-token-parser')(crowi);
 
-  app.get('/_api/lsx', accessTokenParser, loginRequired, lsx.listPages);
+  app.get('/_api/lsx', accessTokenParser, loginRequired, listPages);
 };
 
 export default middleware;

+ 163 - 207
packages/remark-lsx/src/server/routes/lsx.ts

@@ -1,8 +1,11 @@
 
+import type { IPage, IUser } from '@growi/core';
 import { OptionParser } from '@growi/core/dist/plugin';
 import { pathUtils, pagePathUtils } from '@growi/core/dist/utils';
 import escapeStringRegexp from 'escape-string-regexp';
+import type { Request, Response } from 'express';
 import createError, { isHttpError } from 'http-errors';
+import { model } from 'mongoose';
 
 
 const DEFAULT_PAGES_NUM = 50;
@@ -11,254 +14,207 @@ const DEFAULT_PAGES_NUM = 50;
 const { addTrailingSlash } = pathUtils;
 const { isTopPage } = pagePathUtils;
 
-class Lsx {
-
-  /**
-   * add depth condition that limit fetched pages
-   *
-   * @static
-   * @param {any} query
-   * @param {any} pagePath
-   * @param {any} optionsDepth
-   * @returns
-   *
-   * @memberOf Lsx
-   */
-  static addDepthCondition(query, pagePath, optionsDepth) {
-    // when option strings is 'depth=', the option value is true
-    if (optionsDepth == null || optionsDepth === true) {
-      throw createError(400, 'The value of depth option is invalid.');
-    }
+function addDepthCondition(query, pagePath, optionsDepth) {
+  // when option strings is 'depth=', the option value is true
+  if (optionsDepth == null || optionsDepth === true) {
+    throw createError(400, 'The value of depth option is invalid.');
+  }
 
-    const range = OptionParser.parseRange(optionsDepth);
+  const range = OptionParser.parseRange(optionsDepth);
 
-    if (range == null) {
-      return query;
-    }
+  if (range == null) {
+    return query;
+  }
 
-    const start = range.start;
-    const end = range.end;
+  const start = range.start;
+  const end = range.end;
 
-    if (start < 1 || end < 1) {
-      throw createError(400, `specified depth is [${start}:${end}] : start and end are must be larger than 1`);
-    }
+  if (start < 1 || end < 1) {
+    throw createError(400, `specified depth is [${start}:${end}] : start and end are must be larger than 1`);
+  }
 
-    // count slash
-    const slashNum = isTopPage(pagePath)
-      ? 1
-      : pagePath.split('/').length;
-    const depthStart = slashNum; // start is not affect to fetch page
-    const depthEnd = slashNum + end - 1;
+  // count slash
+  const slashNum = isTopPage(pagePath)
+    ? 1
+    : pagePath.split('/').length;
+  const depthStart = slashNum; // start is not affect to fetch page
+  const depthEnd = slashNum + end - 1;
 
-    return query.and({
-      path: new RegExp(`^(\\/[^\\/]*){${depthStart},${depthEnd}}$`),
-    });
+  return query.and({
+    path: new RegExp(`^(\\/[^\\/]*){${depthStart},${depthEnd}}$`),
+  });
+}
+
+/**
+ * add num condition that limit fetched pages
+ */
+function addNumCondition(query, pagePath, optionsNum) {
+  // when option strings is 'num=', the option value is true
+  if (optionsNum == null || optionsNum === true) {
+    throw createError(400, 'The value of num option is invalid.');
   }
 
-  /**
-   * add num condition that limit fetched pages
-   *
-   * @static
-   * @param {any} query
-   * @param {any} pagePath
-   * @param {number|string} optionsNum
-   * @returns
-   *
-   * @memberOf Lsx
-   */
-  static addNumCondition(query, pagePath, optionsNum) {
-    // when option strings is 'num=', the option value is true
-    if (optionsNum == null || optionsNum === true) {
-      throw createError(400, 'The value of num option is invalid.');
-    }
+  if (typeof optionsNum === 'number') {
+    return query.limit(optionsNum);
+  }
 
-    if (typeof optionsNum === 'number') {
-      return query.limit(optionsNum);
-    }
+  const range = OptionParser.parseRange(optionsNum);
 
-    const range = OptionParser.parseRange(optionsNum);
+  if (range == null) {
+    return query;
+  }
 
-    if (range == null) {
-      return query;
-    }
+  const start = range.start;
+  const end = range.end;
 
-    const start = range.start;
-    const end = range.end;
+  if (start < 1 || end < 1) {
+    throw createError(400, `specified num is [${start}:${end}] : start and end are must be larger than 1`);
+  }
 
-    if (start < 1 || end < 1) {
-      throw createError(400, `specified num is [${start}:${end}] : start and end are must be larger than 1`);
-    }
+  const skip = start - 1;
+  const limit = end - skip;
 
-    const skip = start - 1;
-    const limit = end - skip;
+  return query.skip(skip).limit(limit);
+}
 
-    return query.skip(skip).limit(limit);
+/**
+ * add filter condition that filter fetched pages
+ */
+function addFilterCondition(query, pagePath, optionsFilter, isExceptFilter = false) {
+  // when option strings is 'filter=', the option value is true
+  if (optionsFilter == null || optionsFilter === true) {
+    throw createError(400, 'filter option require value in regular expression.');
   }
 
-  /**
-   * add filter condition that filter fetched pages
-   *
-   * @static
-   * @param {any} query
-   * @param {any} pagePath
-   * @param {any} optionsFilter
-   * @param {boolean} isExceptFilter
-   * @returns
-   *
-   * @memberOf Lsx
-   */
-  static addFilterCondition(query, pagePath, optionsFilter, isExceptFilter = false) {
-    // when option strings is 'filter=', the option value is true
-    if (optionsFilter == null || optionsFilter === true) {
-      throw createError(400, 'filter option require value in regular expression.');
-    }
+  const pagePathForRegexp = escapeStringRegexp(addTrailingSlash(pagePath));
 
-    const pagePathForRegexp = escapeStringRegexp(addTrailingSlash(pagePath));
-
-    let filterPath;
-    try {
-      if (optionsFilter.charAt(0) === '^') {
-        // move '^' to the first of path
-        filterPath = new RegExp(`^${pagePathForRegexp}${optionsFilter.slice(1, optionsFilter.length)}`);
-      }
-      else {
-        filterPath = new RegExp(`^${pagePathForRegexp}.*${optionsFilter}`);
-      }
+  let filterPath;
+  try {
+    if (optionsFilter.charAt(0) === '^') {
+      // move '^' to the first of path
+      filterPath = new RegExp(`^${pagePathForRegexp}${optionsFilter.slice(1, optionsFilter.length)}`);
     }
-    catch (err) {
-      throw createError(400, err);
+    else {
+      filterPath = new RegExp(`^${pagePathForRegexp}.*${optionsFilter}`);
     }
+  }
+  catch (err) {
+    throw createError(400, err);
+  }
 
-    if (isExceptFilter) {
-      return query.and({
-        path: { $not: filterPath },
-      });
-    }
+  if (isExceptFilter) {
     return query.and({
-      path: filterPath,
+      path: { $not: filterPath },
     });
   }
+  return query.and({
+    path: filterPath,
+  });
+}
 
-  static addExceptCondition(query, pagePath, optionsFilter) {
-    return this.addFilterCondition(query, pagePath, optionsFilter, true);
-  }
-
-  /**
-   * add sort condition(sort key & sort order)
-   *
-   * If only the reverse option is specified, the sort key is 'path'.
-   * If only the sort key is specified, the sort order is the ascending order.
-   *
-   * @static
-   * @param {any} query
-   * @param {string} pagePath
-   * @param {string} optionsSort
-   * @param {string} optionsReverse
-   * @returns
-   *
-   * @memberOf Lsx
-   */
-  static addSortCondition(query, pagePath, optionsSortArg, optionsReverse) {
-    // init sort key
-    const optionsSort = optionsSortArg ?? 'path';
-
-    // the default sort order
-    const isReversed = optionsReverse === 'true';
-
-    if (optionsSort !== 'path' && optionsSort !== 'createdAt' && optionsSort !== 'updatedAt') {
-      throw createError(400, `The specified value '${optionsSort}' for the sort option is invalid. It must be 'path', 'createdAt' or 'updatedAt'.`);
-    }
+function addExceptCondition(query, pagePath, optionsFilter) {
+  return this.addFilterCondition(query, pagePath, optionsFilter, true);
+}
 
-    const sortOption = {};
-    sortOption[optionsSort] = isReversed ? -1 : 1;
-    return query.sort(sortOption);
+/**
+ * add sort condition(sort key & sort order)
+ *
+ * If only the reverse option is specified, the sort key is 'path'.
+ * If only the sort key is specified, the sort order is the ascending order.
+ *
+ */
+function addSortCondition(query, pagePath, optionsSortArg, optionsReverse) {
+  // init sort key
+  const optionsSort = optionsSortArg ?? 'path';
+
+  // the default sort order
+  const isReversed = optionsReverse === 'true';
+
+  if (optionsSort !== 'path' && optionsSort !== 'createdAt' && optionsSort !== 'updatedAt') {
+    throw createError(400, `The specified value '${optionsSort}' for the sort option is invalid. It must be 'path', 'createdAt' or 'updatedAt'.`);
   }
 
+  const sortOption = {};
+  sortOption[optionsSort] = isReversed ? -1 : 1;
+  return query.sort(sortOption);
 }
 
-// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
-export const routesFactory = (crowi): any => {
-  const Page = crowi.model('Page');
+async function generateBaseQueryBuilder(pagePath: string, user: IUser) {
+  const Page = model<IPage>('Page');
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const actions: any = {};
-
-  /**
-   *
-   * @param {*} pagePath
-   * @param {*} user
-   *
-   * @return {Promise<Query>} query
-   */
-  async function generateBaseQueryBuilder(pagePath, user) {
-    const baseQuery = Page.find();
-
-    const builder = new Page.PageQueryBuilder(baseQuery);
-    builder.addConditionToListOnlyDescendants(pagePath);
-
-    return Page.addConditionToFilteringByViewerForList(builder, user);
-  }
+  const PageAny = Page as any;
 
-  actions.listPages = async(req, res) => {
-    const user = req.user;
+  const baseQuery = Page.find();
 
-    let pagePath;
-    let options;
+  const builder = new PageAny.PageQueryBuilder(baseQuery);
+  builder.addConditionToListOnlyDescendants(pagePath);
 
-    try {
-      pagePath = req.query.pagePath;
-      options = JSON.parse(req.query.options);
-    }
-    catch (error) {
-      return res.status(400).send(error);
+  return PageAny.addConditionToFilteringByViewerForList(builder, user);
+}
+
+export const listPages = async(req: Request & { user: IUser }, res: Response): Promise<Response> => {
+  const Page = model<IPage>('Page');
+
+  const user = req.user;
+
+  let pagePath;
+  let options;
+
+  try {
+    pagePath = req.query.pagePath;
+    if (req.query.options != null) {
+      options = JSON.parse(req.query.options.toString());
     }
+  }
+  catch (error) {
+    return res.status(400).send(error);
+  }
 
-    const builder = await generateBaseQueryBuilder(pagePath, user);
+  const builder = await generateBaseQueryBuilder(pagePath, user);
 
-    // count viewers of `/`
-    let toppageViewersCount;
-    try {
-      const aggRes = await Page.aggregate([
-        { $match: { path: '/' } },
-        { $project: { count: { $size: '$seenUsers' } } },
-      ]);
+  // count viewers of `/`
+  let toppageViewersCount;
+  try {
+    const aggRes = await Page.aggregate([
+      { $match: { path: '/' } },
+      { $project: { count: { $size: '$seenUsers' } } },
+    ]);
+
+    toppageViewersCount = aggRes.length > 0
+      ? aggRes[0].count
+      : 1;
+  }
+  catch (error) {
+    return res.status(500).send(error);
+  }
 
-      toppageViewersCount = aggRes.length > 0
-        ? aggRes[0].count
-        : 1;
+  let query = builder.query;
+  try {
+    // depth
+    if (options.depth != null) {
+      query = addDepthCondition(query, pagePath, options.depth);
     }
-    catch (error) {
-      return res.status(500).send(error);
+    // filter
+    if (options.filter != null) {
+      query = addFilterCondition(query, pagePath, options.filter);
     }
-
-    let query = builder.query;
-    try {
-      // depth
-      if (options.depth != null) {
-        query = Lsx.addDepthCondition(query, pagePath, options.depth);
-      }
-      // filter
-      if (options.filter != null) {
-        query = Lsx.addFilterCondition(query, pagePath, options.filter);
-      }
-      if (options.except != null) {
-        query = Lsx.addExceptCondition(query, pagePath, options.except);
-      }
-      // num
-      const optionsNum = options.num || DEFAULT_PAGES_NUM;
-      query = Lsx.addNumCondition(query, pagePath, optionsNum);
-      // sort
-      query = Lsx.addSortCondition(query, pagePath, options.sort, options.reverse);
-
-      const pages = await query.exec();
-      res.status(200).send({ pages, toppageViewersCount });
+    if (options.except != null) {
+      query = addExceptCondition(query, pagePath, options.except);
     }
-    catch (error) {
-      if (isHttpError) {
-        return res.status(error.status).send(error);
-      }
-      return res.status(500).send(error);
+    // num
+    const optionsNum = options.num || DEFAULT_PAGES_NUM;
+    query = addNumCondition(query, pagePath, optionsNum);
+    // sort
+    query = addSortCondition(query, pagePath, options.sort, options.reverse);
+
+    const pages = await query.exec();
+    return res.status(200).send({ pages, toppageViewersCount });
+  }
+  catch (error) {
+    if (isHttpError) {
+      return res.status(error.status).send(error);
     }
-  };
+    return res.status(500).send(error);
+  }
 
-  return actions;
 };

+ 3 - 1
packages/remark-lsx/vite.server.config.ts

@@ -22,11 +22,13 @@ export default defineConfig({
         preserveModulesRoot: 'src/server',
       },
       external: [
+        'react',
         'axios',
         'escape-string-regexp',
+        'express',
         'http-errors',
         'is-absolute-url',
-        'react',
+        'mongoose',
         'next/link',
         'unified',
         'swr',