Просмотр исходного кода

Merge branch 'master' into feat/page-bulk-export-pnpm

Futa Arai 1 год назад
Родитель
Сommit
f770848588
100 измененных файлов с 441 добавлено и 243 удалено
  1. 0 1
      .changeset/config.json
  2. 0 1
      .devcontainer/postCreateCommand.sh
  3. 3 3
      .github/workflows/ci-app.yml
  4. 7 13
      .github/workflows/ci-slackbot-proxy.yml
  5. 1 1
      .github/workflows/release-slackbot-proxy.yml
  6. 2 2
      .github/workflows/release-subpackages.yml
  7. 2 2
      .github/workflows/release.yml
  8. 35 33
      .github/workflows/reusable-app-prod.yml
  9. 1 1
      .github/workflows/reusable-app-reg-suit.yml
  10. 2 0
      .gitignore
  11. 2 2
      apps/app/docker/Dockerfile
  12. 5 2
      apps/app/next.config.js
  13. 15 6
      apps/app/package.json
  14. 3 3
      apps/app/resource/locales/en_US/sandbox-diagrams.md
  15. 1 1
      apps/app/resource/locales/en_US/sandbox-math.md
  16. 3 3
      apps/app/resource/locales/fr_FR/sandbox-diagrams.md
  17. 1 1
      apps/app/resource/locales/fr_FR/sandbox-math.md
  18. 3 3
      apps/app/resource/locales/ja_JP/sandbox-diagrams.md
  19. 3 3
      apps/app/resource/locales/zh_CN/sandbox-diagrams.md
  20. 1 1
      apps/app/resource/locales/zh_CN/sandbox-math.md
  21. 16 0
      apps/app/src/features/openai/server/models/vector-store-file-relation.ts
  22. 1 1
      apps/app/src/features/openai/server/routes/message.ts
  23. 1 1
      apps/app/src/features/openai/server/routes/rebuild-vector-store.ts
  24. 1 1
      apps/app/src/features/openai/server/routes/thread.ts
  25. 0 0
      apps/app/src/features/openai/server/services/markdown-splitter/markdown-splitter.spec.ts
  26. 14 7
      apps/app/src/features/openai/server/services/markdown-splitter/markdown-splitter.ts
  27. 0 0
      apps/app/src/features/openai/server/services/markdown-splitter/markdown-token-splitter.spec.ts
  28. 2 2
      apps/app/src/features/openai/server/services/markdown-splitter/markdown-token-splitter.ts
  29. 23 10
      apps/app/src/features/openai/server/services/openai.ts
  30. 8 7
      apps/app/src/features/questionnaire/server/routes/apiv3/questionnaire.ts
  31. 1 1
      apps/app/src/migrations/20180926134048-make-email-unique.js
  32. 2 3
      apps/app/src/migrations/20180927102719-init-serverurl.js
  33. 2 2
      apps/app/src/migrations/20181019114028-abolish-page-group-relation.js
  34. 1 1
      apps/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js
  35. 2 2
      apps/app/src/migrations/20190618104011-add-config-app-installed.js
  36. 1 1
      apps/app/src/migrations/20190619055421-adjust-page-grant.js
  37. 1 1
      apps/app/src/migrations/20190624110950-fill-last-update-user.js
  38. 1 1
      apps/app/src/migrations/20190629193445-make-root-page-public.js
  39. 1 1
      apps/app/src/migrations/20191102223900-drop-configs-indices.js
  40. 1 1
      apps/app/src/migrations/20191102223901-drop-pages-indices.js
  41. 1 1
      apps/app/src/migrations/20191126173016-adjust-pages-path.js
  42. 1 1
      apps/app/src/migrations/20191127023815-drop-wrong-index-of-page-tag-relation.js
  43. 1 1
      apps/app/src/migrations/20200402160380-remove-deleteduser-from-relationgroup.js
  44. 1 1
      apps/app/src/migrations/20200420160390-remove-crowi-layout.js
  45. 2 2
      apps/app/src/migrations/20200512005851-remove-behavior-type.js
  46. 1 1
      apps/app/src/migrations/20200514001356-update-theme-color-for-dark.js
  47. 1 1
      apps/app/src/migrations/20200620203632-normalize-locale-id.js
  48. 2 2
      apps/app/src/migrations/20200827045151-remove-layout-setting.js
  49. 2 2
      apps/app/src/migrations/20200828024025-copy-aws-setting.js
  50. 2 2
      apps/app/src/migrations/20200901034313-update-mail-transmission.js
  51. 2 2
      apps/app/src/migrations/20200903080025-remove-timeline-type.js.js
  52. 2 2
      apps/app/src/migrations/20200915035234-rename-s3-config.js
  53. 1 1
      apps/app/src/migrations/20210420160380-convert-double-to-date.js
  54. 2 2
      apps/app/src/migrations/20210830074539-update-configs-for-slackbot.js
  55. 1 1
      apps/app/src/migrations/20210906194521-slack-app-integration-set-default-value.js
  56. 2 2
      apps/app/src/migrations/20210921173042-add-is-trashed-field.js
  57. 2 2
      apps/app/src/migrations/20211005120030-slack-app-integration-rename-keys.js
  58. 2 2
      apps/app/src/migrations/20211005131430-config-without-proxy-command-permission-for-renaming.js
  59. 2 2
      apps/app/src/migrations/20211129125654-initialize-private-legacy-pages-named-query.js
  60. 2 2
      apps/app/src/migrations/20211227060705-revision-path-to-page-id-schema-migration--fixed-7549.js
  61. 2 2
      apps/app/src/migrations/20220131001218-convert-redirect-to-pages-to-page-redirect-documents.js
  62. 1 1
      apps/app/src/migrations/20220311011114-convert-page-delete-config.js
  63. 1 1
      apps/app/src/migrations/20220411114257-set-sparse-option-to-slack-member-id.js
  64. 1 1
      apps/app/src/migrations/20220613064207-add-attachment-type-to-existing-attachments.js
  65. 2 2
      apps/app/src/migrations/20221014130200-remove-customize-is-saved-states-of-tab-changes.js
  66. 1 1
      apps/app/src/migrations/20221219011829-remove-basic-auth-related-config.js
  67. 2 2
      apps/app/src/migrations/20230213090921-remove-presentation-configurations.js
  68. 2 2
      apps/app/src/migrations/20230731075753-add_installed_date_to_config.js
  69. 1 1
      apps/app/src/migrations/20231102012742-clean-user-ui-settings-collection.js
  70. 9 0
      apps/app/src/server/crowi/index.js
  71. 0 37
      apps/app/src/server/middlewares/access-token-parser.js
  72. 135 0
      apps/app/src/server/middlewares/access-token-parser/access-token-parser.integ.ts
  73. 38 0
      apps/app/src/server/middlewares/access-token-parser/access-token-parser.ts
  74. 1 0
      apps/app/src/server/middlewares/access-token-parser/index.ts
  75. 14 0
      apps/app/src/server/middlewares/access-token-parser/interfaces.ts
  76. 5 11
      apps/app/src/server/models/user.js
  77. 1 1
      apps/app/src/server/routes/apiv3/activity.ts
  78. 1 1
      apps/app/src/server/routes/apiv3/app-settings.js
  79. 1 1
      apps/app/src/server/routes/apiv3/attachment.js
  80. 1 1
      apps/app/src/server/routes/apiv3/bookmark-folder.ts
  81. 1 1
      apps/app/src/server/routes/apiv3/bookmarks.js
  82. 1 1
      apps/app/src/server/routes/apiv3/export.js
  83. 1 1
      apps/app/src/server/routes/apiv3/g2g-transfer.ts
  84. 1 1
      apps/app/src/server/routes/apiv3/import.js
  85. 1 1
      apps/app/src/server/routes/apiv3/in-app-notification.ts
  86. 1 1
      apps/app/src/server/routes/apiv3/page-listing.ts
  87. 2 2
      apps/app/src/server/routes/apiv3/page/check-page-existence.ts
  88. 1 1
      apps/app/src/server/routes/apiv3/page/create-page.ts
  89. 1 1
      apps/app/src/server/routes/apiv3/page/get-yjs-data.ts
  90. 1 1
      apps/app/src/server/routes/apiv3/page/index.ts
  91. 1 1
      apps/app/src/server/routes/apiv3/page/publish-page.ts
  92. 1 1
      apps/app/src/server/routes/apiv3/page/sync-latest-revision-body-to-yjs-draft.ts
  93. 1 1
      apps/app/src/server/routes/apiv3/page/unpublish-page.ts
  94. 1 1
      apps/app/src/server/routes/apiv3/page/update-page.ts
  95. 1 1
      apps/app/src/server/routes/apiv3/pages/index.js
  96. 1 1
      apps/app/src/server/routes/apiv3/personal-setting.js
  97. 1 1
      apps/app/src/server/routes/apiv3/revisions.js
  98. 1 1
      apps/app/src/server/routes/apiv3/search.js
  99. 1 1
      apps/app/src/server/routes/apiv3/slack-integration-settings.js
  100. 1 1
      apps/app/src/server/routes/apiv3/users.js

+ 0 - 1
.changeset/config.json

@@ -15,7 +15,6 @@
     "@growi/app",
     "@growi/slackbot-proxy",
     "@growi/custom-icons",
-    "@growi/markdown-splitter",
     "@growi/editor",
     "@growi/presentation",
     "@growi/preset-templates",

+ 0 - 1
.devcontainer/postCreateCommand.sh

@@ -15,5 +15,4 @@ eval "$(cat /home/vscode/.bashrc)"
 pnpm install turbo --global
 
 # Install dependencies
-git-lfs pull
 turbo run bootstrap

+ 3 - 3
.github/workflows/ci-app.yml

@@ -55,7 +55,7 @@ jobs:
       - name: Install dependencies
         run: |
           pnpm add turbo --global
-          pnpm install
+          pnpm install --frozen-lockfile
 
       - name: Lint
         run: |
@@ -109,7 +109,7 @@ jobs:
       - name: Install dependencies
         run: |
           pnpm add turbo --global
-          pnpm install
+          pnpm install --frozen-lockfile
 
       - name: Test
         run: |
@@ -173,7 +173,7 @@ jobs:
       - name: Install dependencies
         run: |
           pnpm add turbo --global
-          pnpm install
+          pnpm install --frozen-lockfile
 
       - name: turbo run launch-dev:ci
         working-directory: ./apps/app

+ 7 - 13
.github/workflows/ci-slackbot-proxy.yml

@@ -55,7 +55,7 @@ jobs:
     - name: Install dependencies
       run: |
         pnpm add turbo --global
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Lint
       run: |
@@ -120,7 +120,7 @@ jobs:
     - name: Install dependencies
       run: |
         pnpm add turbo --global
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: turbo run dev:ci
       working-directory: ./apps/slackbot-proxy
@@ -189,16 +189,9 @@ jobs:
       run: |
         pnpm add turbo --global
 
-    - name: Prune repositories
-      run: |
-        turbo prune @growi/slackbot-proxy
-        rm -rf apps packages
-        mv out/* .
-
     - name: Install dependencies
-      # Run pnpm install with `--no-frozen-lockfile` option after `turbo prune` to avoid ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY
       run: |
-        pnpm install --no-frozen-lockfile
+        pnpm install --frozen-lockfile
 
     - name: Restore dist
       uses: actions/cache/restore@v4
@@ -215,10 +208,11 @@ jobs:
       run: |
         turbo run build
 
-    - name: Install dependencies for production
-      # Run pnpm install with `--no-frozen-lockfile` option after `turbo prune` to avoid ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY
+    - name: Assembling all dependencies
       run: |
-        pnpm install --no-frozen-lockfile --prod
+        rm -rf out
+        pnpm deploy out --prod --filter @growi/slackbot-proxy
+        rm -rf apps/slackbot-proxy/node_modules && mv out/node_modules apps/slackbot-proxy/node_modules
 
     - name: pnpm run start:prod:ci
       working-directory: ./apps/slackbot-proxy

+ 1 - 1
.github/workflows/release-slackbot-proxy.yml

@@ -103,7 +103,7 @@ jobs:
     - name: Install dependencies
       run: |
         pnpm add turbo --global
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Bump versions for next RC
       run: |

+ 2 - 2
.github/workflows/release-subpackages.yml

@@ -38,7 +38,7 @@ jobs:
     - name: Install dependencies
       run: |
         pnpm add turbo --global
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Setup .npmrc
       run: |
@@ -81,7 +81,7 @@ jobs:
     - name: Install dependencies
       run: |
         pnpm add turbo --global
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Create Release Pull Request or Publish to npm
       id: changesets

+ 2 - 2
.github/workflows/release.yml

@@ -32,7 +32,7 @@ jobs:
     - name: Install dependencies
       run: |
         pnpm add turbo --global
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Bump versions
       run: |
@@ -172,7 +172,7 @@ jobs:
     - name: Install dependencies
       run: |
         pnpm add turbo --global
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Bump versions for next RC
       run: |

+ 35 - 33
.github/workflows/reusable-app-prod.yml

@@ -19,12 +19,14 @@ jobs:
 
     outputs:
       PROD_FILES: ${{ steps.archive-prod-files.outputs.file }}
+      PROD_DEPS: ${{ steps.archive-prod-deps.outputs.file }}
 
     steps:
     - uses: actions/checkout@v4
-      with:
-        # retrieve local font files
-        lfs: true
+
+    - name: Install Git LFS
+      run: |
+        git lfs install
 
     - uses: pnpm/action-setup@v4
 
@@ -37,16 +39,9 @@ jobs:
       run: |
         pnpm add turbo --global
 
-    - name: Prune repositories
-      run: |
-        turbo prune @growi/app
-        rm -rf apps packages
-        mv out/* .
-
     - name: Install dependencies
-      # Run pnpm install with `--no-frozen-lockfile` option after `turbo prune` to avoid ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY
       run: |
-        pnpm install --no-frozen-lockfile
+        pnpm install --frozen-lockfile
 
     - name: Cache/Restore dist
       uses: actions/cache@v4
@@ -67,6 +62,12 @@ jobs:
       env:
         ANALYZE: 1
 
+    - name: Assembling all dependencies
+      run: |
+        rm -rf out
+        pnpm deploy out --prod --filter @growi/app
+        rm -rf apps/app/node_modules && mv out/node_modules apps/app/node_modules
+
     - name: Archive production files
       id: archive-prod-files
       run: |
@@ -79,24 +80,34 @@ jobs:
           apps/app/resource \
           apps/app/tmp \
           apps/app/.env.production* \
-          apps/app/package.json \
-          packages/*/dist \
-          packages/*/package.json
+          apps/app/package.json
         echo "file=production.tar.gz" >> $GITHUB_OUTPUT
 
+    - name: Archive production dependencies
+      id: archive-prod-deps
+      run: |
+        tar -zcf production-deps.tar.gz \
+          apps/app/node_modules
+        echo "file=production-deps.tar.gz" >> $GITHUB_OUTPUT
+
     - name: Upload production files as artifact
       uses: actions/upload-artifact@v4
       with:
         name: Production Files (node${{ inputs.node-version }})
         path: ${{ steps.archive-prod-files.outputs.file }}
 
+    - name: Upload production dependencies as artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: Production Dependencies (node${{ inputs.node-version }})
+        path: ${{ steps.archive-prod-deps.outputs.file }}
+
     - name: Upload report as artifact
       uses: actions/upload-artifact@v4
       with:
         name: Bundle Analyzing Report (node${{ inputs.node-version }})
         path: |
-          apps/app/.next/analyze/client.html
-          apps/app/.next/analyze/server.html
+          apps/app/.next/analyze
 
     - name: Slack Notification
       uses: weseek/ghaction-slack-notification@master
@@ -135,29 +146,20 @@ jobs:
         node-version: ${{ inputs.node-version }}
         cache: 'pnpm'
 
-    - name: Install turbo
-      run: |
-        pnpm add turbo --global
-
-    - name: Prune repositories
-      run: |
-        turbo prune @growi/app
-        rm -rf apps packages
-        mv out/* .
-
-    - name: Install dependencies
-      # Run pnpm install with `--no-frozen-lockfile` option after `turbo prune` to avoid ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY
-      run: |
-        pnpm install --no-frozen-lockfile --prod
-
     - name: Download production files artifact
       uses: actions/download-artifact@v4
       with:
         name: Production Files (node${{ inputs.node-version }})
 
-    - name: Extract procution files artifact
+    - name: Download production dependencies artifact
+      uses: actions/download-artifact@v4
+      with:
+        name: Production Dependencies (node${{ inputs.node-version }})
+
+    - name: Extract procution files and dependencies
       run: |
         tar -xf ${{ needs.build-prod.outputs.PROD_FILES }}
+        tar -xf ${{ needs.build-prod.outputs.PROD_DEPS }}
 
     - name: pnpm run server:ci
       working-directory: ./apps/app
@@ -220,7 +222,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Install Playwright browsers
       run: |

+ 1 - 1
.github/workflows/reusable-app-reg-suit.yml

@@ -63,7 +63,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        pnpm install
+        pnpm install --frozen-lockfile
 
     - name: Download screenshots taken by cypress
       uses: actions/download-artifact@v4

+ 2 - 0
.gitignore

@@ -41,4 +41,6 @@ yarn-error.log*
 
 # turborepo
 .turbo
+
+# pnpm deploy target dir
 out

+ 2 - 2
apps/app/docker/Dockerfile

@@ -40,8 +40,8 @@ RUN turbo run clean
 RUN turbo run build --filter @growi/app
 
 # make artifacts
-RUN pnpm --filter @growi/app --prod deploy pruned
-RUN rm -rf apps/app/node_modules && mv pruned/node_modules apps/app/node_modules
+RUN pnpm deploy out --prod --filter @growi/app
+RUN rm -rf apps/app/node_modules && mv out/node_modules apps/app/node_modules
 RUN rm -rf apps/app/.next/cache
 RUN tar -zcf packages.tar.gz \
   package.json \

+ 5 - 2
apps/app/next.config.js

@@ -73,7 +73,6 @@ const getTranspilePackages = () => {
 const optimizePackageImports = [
   '@growi/core',
   '@growi/editor',
-  '@growi/markdown-splitter',
   '@growi/pluginkit',
   '@growi/presentation',
   '@growi/preset-themes',
@@ -160,7 +159,11 @@ module.exports = async(phase, { defaultConfig }) => {
   }
 
   const withBundleAnalyzer = require('@next/bundle-analyzer')({
-    enabled: phase === PHASE_PRODUCTION_BUILD && process.env.ANALYZE === 'true',
+    enabled: phase === PHASE_PRODUCTION_BUILD
+      && (
+        process.env.ANALYZE === 'true'
+          || process.env.ANALYZE === '1'
+      ),
   });
 
   return withBundleAnalyzer(withSuperjson()(nextConfig));

+ 15 - 6
apps/app/package.json

@@ -14,11 +14,12 @@
     "server": "cross-env NODE_ENV=production node -r dotenv-flow/config dist/server/app.js",
     "server:ci": "pnpm run server --ci",
     "preserver": "cross-env NODE_ENV=production pnpm run migrate",
-    "styles-prebuilt": "vite build -c vite.styles-prebuilt.config.ts",
+    "pre:styles": "vite build -c vite.styles-prebuilt.config.ts",
+    "pre:lfs": "git lfs pull",
     "migrate": "node -r dotenv-flow/config node_modules/migrate-mongo/bin/migrate-mongo up -f config/migrate-mongo-config.js",
     "//// for development": "",
     "dev": "cross-env NODE_ENV=development nodemon --exec pnpm run ts-node --inspect src/server/app.ts",
-    "dev:styles-prebuilt": "pnpm run styles-prebuilt --mode dev",
+    "dev:pre:styles": "pnpm run pre:styles --mode dev",
     "dev:migrate-mongo": "cross-env NODE_ENV=development pnpm run ts-node node_modules/migrate-mongo/bin/migrate-mongo",
     "dev:migrate": "pnpm run dev:migrate:status > tmp/cache/migration-status.out && pnpm run dev:migrate:up",
     "dev:migrate:create": "pnpm run dev:migrate-mongo create -f config/migrate-mongo-config.js",
@@ -67,6 +68,7 @@
     "@azure/openai": "^2.0.0-beta.2",
     "@azure/storage-blob": "^12.16.0",
     "@browser-bunyan/console-formatted-stream": "^1.8.0",
+    "@cspell/dynamic-import": "^8.15.4",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@elastic/elasticsearch8": "npm:@elastic/elasticsearch@^8.7.0",
     "@godaddy/terminus": "^4.9.0",
@@ -101,6 +103,7 @@
     "connect-mongo": "^4.6.0",
     "connect-redis": "^4.0.4",
     "cookie-parser": "^1.4.5",
+    "cross-env": "^7.0.0",
     "csurf": "^1.11.0",
     "csv-to-markdown-table": "^1.4.1",
     "date-fns": "^3.6.0",
@@ -108,6 +111,7 @@
     "detect-indent": "^7.0.0",
     "diff": "^5.0.0",
     "diff_match_patch": "^0.1.1",
+    "dotenv-flow": "^3.2.0",
     "ejs": "^3.1.10",
     "esa-node": "^0.2.2",
     "escape-string-regexp": "^4.0.0",
@@ -129,14 +133,16 @@
     "i18next-resources-to-backend": "^1.2.1",
     "is-absolute-url": "^4.0.1",
     "is-iso-date": "^0.0.1",
+    "js-tiktoken": "^1.0.15",
+    "js-yaml": "^4.1.0",
     "katex": "^0.16.11",
     "ldapjs": "^3.0.2",
     "lucene-query-parser": "^1.2.0",
     "markdown-table": "^3.0.3",
+    "md5": "^2.2.1",
     "mdast-util-from-markdown": "^2.0.1",
     "mdast-util-gfm-table": "^2.0.0",
     "mdast-util-wiki-link": "^0.1.2",
-    "md5": "^2.2.1",
     "mermaid": "^11.2.0",
     "method-override": "^3.0.0",
     "micromark-extension-gfm-table": "^2.1.0",
@@ -197,12 +203,12 @@
     "rehype-toc": "^3.0.2",
     "remark-breaks": "^4.0.0",
     "remark-directive": "^3.0.0",
-    "remark-emoji": "^5.0.0",
     "remark-frontmatter": "^5.0.0",
     "remark-gfm": "^4.0.0",
     "remark-math": "^6.0.0",
     "remark-parse": "^11.0.0",
     "remark-rehype": "^11.1.1",
+    "remark-stringify": "^11.0.0",
     "remark-toc": "^9.0.0",
     "sanitize-filename": "^1.6.3",
     "socket.io": "^4.7.5",
@@ -212,8 +218,10 @@
     "swagger-jsdoc": "^6.2.8",
     "swr": "^2.2.2",
     "throttle-debounce": "^5.0.0",
-    "uid-safe": "^2.1.5",
+    "ts-deepmerge": "^6.2.0",
+    "tslib": "^2.8.0",
     "uglifycss": "^0.0.29",
+    "uid-safe": "^2.1.5",
     "unified": "^11.0.0",
     "unist-util-visit": "^5.0.0",
     "universal-bunyan": "^0.9.2",
@@ -235,10 +243,10 @@
     "mongodb": "mongoose which is used requires mongo@4.16.0."
   },
   "devDependencies": {
+    "@emoji-mart/data": "^1.2.1",
     "@growi/core-styles": "workspace:^",
     "@growi/custom-icons": "workspace:^",
     "@growi/editor": "workspace:^",
-    "@growi/markdown-splitter": "workspace:^",
     "@growi/ui": "workspace:^",
     "@handsontable/react": "=2.1.0",
     "@next/bundle-analyzer": "^14.1.3",
@@ -287,6 +295,7 @@
     "load-css-file": "^1.0.0",
     "material-icons": "^1.11.3",
     "mdast-util-directive": "^3.0.0",
+    "mdast-util-find-and-replace": "^3.0.1",
     "mongodb-memory-server-core": "^9.1.1",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",

+ 3 - 3
apps/app/resource/locales/en_US/sandbox-diagrams.md

@@ -1,4 +1,4 @@
-# :pencil: diagrams.net(Draw.io)
+# :pencil2: diagrams.net(Draw.io)
 
 See [diagrams.net](https://diagrams.net)
 
@@ -23,7 +23,7 @@ See [diagrams.net](https://diagrams.net)
 
 
 
-# :pencil: PlantUML
+# :pencil2: PlantUML
 
 See [PlantUML](http://plantuml.com/).
 
@@ -151,7 +151,7 @@ State3 --> [*] : Aborted
 
 
 
-# :pencil: Mermaid
+# :pencil2: Mermaid
 
 ## Pie graph
 

+ 1 - 1
apps/app/resource/locales/en_US/sandbox-math.md

@@ -1,4 +1,4 @@
-# :pencil: Math
+# :pencil2: Math
 
 See [KaTeX](https://katex.org/).
 

+ 3 - 3
apps/app/resource/locales/fr_FR/sandbox-diagrams.md

@@ -1,4 +1,4 @@
-# :pencil: diagrams.net(Draw.io)
+# :pencil2: diagrams.net(Draw.io)
 
 See [diagrams.net](https://diagrams.net)
 
@@ -23,7 +23,7 @@ See [diagrams.net](https://diagrams.net)
 
 
 
-# :pencil: PlantUML
+# :pencil2: PlantUML
 
 See [PlantUML](http://plantuml.com/).
 
@@ -151,7 +151,7 @@ State3 --> [*] : Aborted
 
 
 
-# :pencil: Mermaid
+# :pencil2: Mermaid
 
 ## Pie graph
 

+ 1 - 1
apps/app/resource/locales/fr_FR/sandbox-math.md

@@ -1,4 +1,4 @@
-# :pencil: Math
+# :pencil2: Math
 
 See [KaTeX](https://katex.org/).
 

+ 3 - 3
apps/app/resource/locales/ja_JP/sandbox-diagrams.md

@@ -2,7 +2,7 @@
 - GROWI では各種機能を活用することで様々な図形の表現が可能です
   - 各種機能の特色を活かして図形の表現をしましょう
 
-# :pencil: Diagrams.net(旧 Draw.io)
+# :pencil2: Diagrams.net(旧 Draw.io)
 - 図形の挿入時に全般的にご利用いただきやすい図形の挿入方法となります
   - サービスの詳細は [こちら](https://www.drawio.com/) をご確認ください
 - Edit 画面下部のツールバーより専用の編集画面を用いて図形を編集することが可能です
@@ -28,7 +28,7 @@ pLzXsqNMti76NH0Pwggu8d4Jzx3eCO/R05/M+XevvTtWnIgTcapK0wCCNGN8ZmSq/oVxwy2t6dwYU1H2
 ```
 
 
-# :pencil: Mermaid
+# :pencil2: Mermaid
 - Mermaidとは、Markdownテキストでグラフを作成できるダイアグラムツールです
   - サービスの詳細は [こちら](https://mermaid.js.org/) をご確認ください
 
@@ -79,7 +79,7 @@ mindmap
 ```
 
 
-# :pencil: PlantUML
+# :pencil2: PlantUML
 - PlantUML はオープンソースの UML 描画ツールです
   - サービスの詳細は [こちら](https://plantuml.com/) をご確認ください
 

+ 3 - 3
apps/app/resource/locales/zh_CN/sandbox-diagrams.md

@@ -1,4 +1,4 @@
-# :pencil: diagrams.net(Draw.io)
+# :pencil2: diagrams.net(Draw.io)
 
 See [diagrams.net](https://diagrams.net)
 
@@ -23,7 +23,7 @@ See [diagrams.net](https://diagrams.net)
 
 
 
-# :pencil: PlantUML
+# :pencil2: PlantUML
 
 See [PlantUML](http://plantuml.com/).
 
@@ -151,7 +151,7 @@ State3 --> [*] : Aborted
 
 
 
-# :pencil: Mermaid
+# :pencil2: Mermaid
 
 ## Pie graph
 

+ 1 - 1
apps/app/resource/locales/zh_CN/sandbox-math.md

@@ -1,4 +1,4 @@
-# :pencil: Math
+# :pencil2: Math
 
 See [KaTeX](https://katex.org/).
 

+ 16 - 0
apps/app/src/features/openai/server/models/vector-store-file-relation.ts

@@ -7,12 +7,14 @@ import { getOrCreateModel } from '~/server/util/mongoose-utils';
 export interface VectorStoreFileRelation {
   pageId: mongoose.Types.ObjectId;
   fileIds: string[];
+  isAttachedToVectorStore: boolean;
 }
 
 interface VectorStoreFileRelationDocument extends VectorStoreFileRelation, Document {}
 
 interface VectorStoreFileRelationModel extends Model<VectorStoreFileRelation> {
   upsertVectorStoreFileRelations(vectorStoreFileRelations: VectorStoreFileRelation[]): Promise<void>;
+  markAsAttachedToVectorStore(pageIds: Types.ObjectId[]): Promise<void>;
 }
 
 export const prepareVectorStoreFileRelations = (
@@ -30,6 +32,7 @@ export const prepareVectorStoreFileRelations = (
     relationsMap.set(pageIdStr, {
       pageId,
       fileIds: [fileId],
+      isAttachedToVectorStore: false,
     });
   }
 
@@ -47,6 +50,11 @@ const schema = new Schema<VectorStoreFileRelationDocument, VectorStoreFileRelati
     type: String,
     required: true,
   }],
+  isAttachedToVectorStore: {
+    type: Boolean,
+    default: false, // File is not attached to the Vector Store at the time it is uploaded
+    required: true,
+  },
 });
 
 schema.statics.upsertVectorStoreFileRelations = async function(vectorStoreFileRelations: VectorStoreFileRelation[]): Promise<void> {
@@ -63,4 +71,12 @@ schema.statics.upsertVectorStoreFileRelations = async function(vectorStoreFileRe
   );
 };
 
+// Used when attached to VectorStore
+schema.statics.markAsAttachedToVectorStore = async function(pageIds: Types.ObjectId[]): Promise<void> {
+  await this.updateMany(
+    { pageId: { $in: pageIds } },
+    { $set: { isAttachedToVectorStore: true } },
+  );
+};
+
 export default getOrCreateModel<VectorStoreFileRelationDocument, VectorStoreFileRelationModel>('VectorStoreFileRelation', schema);

+ 1 - 1
apps/app/src/features/openai/server/routes/message.ts

@@ -7,6 +7,7 @@ import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
 
 import { getOrCreateChatAssistant } from '~/features/openai/server/services/assistant';
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import loggerFactory from '~/utils/logger';
@@ -30,7 +31,6 @@ type Req = Request<undefined, Response, ReqBody>
 type PostMessageHandlersFactory = (crowi: Crowi) => RequestHandler[];
 
 export const postMessageHandlersFactory: PostMessageHandlersFactory = (crowi) => {
-  const accessTokenParser = require('~/server/middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
 
   const validator: ValidationChain[] = [

+ 1 - 1
apps/app/src/features/openai/server/routes/rebuild-vector-store.ts

@@ -3,6 +3,7 @@ import type { Request, RequestHandler } from 'express';
 import type { ValidationChain } from 'express-validator';
 
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import loggerFactory from '~/utils/logger';
@@ -16,7 +17,6 @@ const logger = loggerFactory('growi:routes:apiv3:openai:rebuild-vector-store');
 type RebuildVectorStoreFactory = (crowi: Crowi) => RequestHandler[];
 
 export const rebuildVectorStoreHandlersFactory: RebuildVectorStoreFactory = (crowi) => {
-  const accessTokenParser = require('~/server/middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
   const adminRequired = require('~/server/middlewares/admin-required')(crowi);
 

+ 1 - 1
apps/app/src/features/openai/server/routes/thread.ts

@@ -5,6 +5,7 @@ import { body } from 'express-validator';
 import { filterXSS } from 'xss';
 
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import loggerFactory from '~/utils/logger';
@@ -20,7 +21,6 @@ type CreateThreadReq = Request<undefined, ApiV3Response, { threadId?: string }>
 type CreateThreadFactory = (crowi: Crowi) => RequestHandler[];
 
 export const createThreadHandlersFactory: CreateThreadFactory = (crowi) => {
-  const accessTokenParser = require('~/server/middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi);
 
   const validator: ValidationChain[] = [

+ 0 - 0
packages/markdown-splitter/src/services/markdown-splitter.spec.ts → apps/app/src/features/openai/server/services/markdown-splitter/markdown-splitter.spec.ts


+ 14 - 7
packages/markdown-splitter/src/services/markdown-splitter.ts → apps/app/src/features/openai/server/services/markdown-splitter/markdown-splitter.ts

@@ -1,12 +1,13 @@
+import { dynamicImport } from '@cspell/dynamic-import';
 import type { TiktokenModel } from 'js-tiktoken';
 import { encodingForModel } from 'js-tiktoken';
 import yaml from 'js-yaml';
-import remarkFrontmatter from 'remark-frontmatter'; // Frontmatter processing
-import remarkGfm from 'remark-gfm'; // GFM processing
-import remarkParse from 'remark-parse';
-import type { Options as StringifyOptions } from 'remark-stringify';
-import remarkStringify from 'remark-stringify';
-import { unified } from 'unified';
+import type * as RemarkFrontmatter from 'remark-frontmatter';
+import type * as RemarkGfm from 'remark-gfm';
+import type * as RemarkParse from 'remark-parse';
+import type * as RemarkStringify from 'remark-stringify';
+import type * as Unified from 'unified';
+
 
 export type MarkdownFragment = {
   label: string;
@@ -59,12 +60,18 @@ export async function splitMarkdownIntoFragments(markdownText: string, model: Ti
 
   const encoder = encodingForModel(model);
 
+  const remarkParse = (await dynamicImport<typeof RemarkParse>('remark-parse', __dirname)).default;
+  const remarkFrontmatter = (await dynamicImport<typeof RemarkFrontmatter>('remark-frontmatter', __dirname)).default;
+  const remarkGfm = (await dynamicImport<typeof RemarkGfm>('remark-gfm', __dirname)).default;
+  const remarkStringify = (await dynamicImport<typeof RemarkStringify>('remark-stringify', __dirname)).default;
+  const unified = (await dynamicImport<typeof Unified>('unified', __dirname)).unified;
+
   const parser = unified()
     .use(remarkParse)
     .use(remarkFrontmatter, ['yaml'])
     .use(remarkGfm); // Enable GFM extensions
 
-  const stringifyOptions: StringifyOptions = {
+  const stringifyOptions: RemarkStringify.Options = {
     bullet: '-', // Set list bullet to hyphen
     rule: '-', // Use hyphen for horizontal rules
   };

+ 0 - 0
packages/markdown-splitter/src/services/markdown-token-splitter.spec.ts → apps/app/src/features/openai/server/services/markdown-splitter/markdown-token-splitter.spec.ts


+ 2 - 2
packages/markdown-splitter/src/services/markdown-token-splitter.ts → apps/app/src/features/openai/server/services/markdown-splitter/markdown-token-splitter.ts

@@ -105,7 +105,7 @@ export async function splitMarkdownIntoChunks(
 
   // Split markdown text into chunks
   const markdownFragments = await splitMarkdownIntoFragments(markdownText, model);
-  const chunks = [] as string[];
+  const chunks: string[] = [];
 
   // Group the chunks based on token count
   const fragmentGroupes = groupMarkdownFragments(markdownFragments, maxToken);
@@ -162,7 +162,7 @@ export async function splitMarkdownIntoChunks(
             const charCountForSplit = Math.floor((remainingTokenCount / fragmenTokenCount) * fragmentCharCount);
 
             // Split content based on character count
-            const splitContents = [];
+            const splitContents: string[] = [];
             for (let i = 0; i < fragment.text.length; i += charCountForSplit) {
               splitContents.push(fragment.text.slice(i, i + charCountForSplit));
             }

+ 23 - 10
apps/app/src/features/openai/server/services/openai.ts

@@ -21,6 +21,7 @@ import loggerFactory from '~/utils/logger';
 import { OpenaiServiceTypes } from '../../interfaces/ai';
 
 import { getClient } from './client-delegator';
+import { splitMarkdownIntoChunks } from './markdown-splitter/markdown-token-splitter';
 import { oepnaiApiErrorHandler } from './openai-api-error-handler';
 
 const BATCH_SIZE = 100;
@@ -29,6 +30,8 @@ const logger = loggerFactory('growi:service:openai');
 
 let isVectorStoreForPublicScopeExist = false;
 
+type VectorStoreFileRelationsMap = Map<string, VectorStoreFileRelation>
+
 export interface IOpenaiService {
   getOrCreateThread(userId: string, vectorStoreId?: string, threadId?: string): Promise<OpenAI.Beta.Threads.Thread | undefined>;
   getOrCreateVectorStoreForPublicScope(): Promise<VectorStoreDocument>;
@@ -134,26 +137,32 @@ class OpenaiService implements IOpenaiService {
     return newVectorStoreDocument;
   }
 
-  private async uploadFile(pageId: Types.ObjectId, body: string): Promise<OpenAI.Files.FileObject> {
-    const file = await toFile(Readable.from(body), `${pageId}.md`);
-    const uploadedFile = await this.client.uploadFile(file);
-    return uploadedFile;
+  private async uploadFileByChunks(pageId: Types.ObjectId, body: string, vectorStoreFileRelationsMap: VectorStoreFileRelationsMap) {
+    const chunks = await splitMarkdownIntoChunks(body, 'gpt-4o');
+    for await (const [index, chunk] of chunks.entries()) {
+      try {
+        const file = await toFile(Readable.from(chunk), `${pageId}-chunk-${index}.md`);
+        const uploadedFile = await this.client.uploadFile(file);
+        prepareVectorStoreFileRelations(pageId, uploadedFile.id, vectorStoreFileRelationsMap);
+      }
+      catch (err) {
+        logger.error(err);
+      }
+    }
   }
 
   async createVectorStoreFile(pages: Array<HydratedDocument<PageDocument>>): Promise<void> {
-    const vectorStoreFileRelationsMap: Map<string, VectorStoreFileRelation> = new Map();
+    const vectorStoreFileRelationsMap: VectorStoreFileRelationsMap = new Map();
     const processUploadFile = async(page: PageDocument) => {
       if (page._id != null && page.grant === PageGrant.GRANT_PUBLIC && page.revision != null) {
         if (isPopulated(page.revision) && page.revision.body.length > 0) {
-          const uploadedFile = await this.uploadFile(page._id, page.revision.body);
-          prepareVectorStoreFileRelations(page._id, uploadedFile.id, vectorStoreFileRelationsMap);
+          await this.uploadFileByChunks(page._id, page.revision.body, vectorStoreFileRelationsMap);
           return;
         }
 
         const pagePopulatedToShowRevision = await page.populateDataToShowRevision();
         if (pagePopulatedToShowRevision.revision != null && pagePopulatedToShowRevision.revision.body.length > 0) {
-          const uploadedFile = await this.uploadFile(page._id, pagePopulatedToShowRevision.revision.body);
-          prepareVectorStoreFileRelations(page._id, uploadedFile.id, vectorStoreFileRelationsMap);
+          await this.uploadFileByChunks(page._id, pagePopulatedToShowRevision.revision.body, vectorStoreFileRelationsMap);
         }
       }
     };
@@ -177,6 +186,8 @@ class OpenaiService implements IOpenaiService {
       return;
     }
 
+    const pageIds = pages.map(page => page._id);
+
     try {
       // Save vector store file relation
       await VectorStoreFileRelationModel.upsertVectorStoreFileRelations(vectorStoreFileRelations);
@@ -185,12 +196,14 @@ class OpenaiService implements IOpenaiService {
       const vectorStore = await this.getOrCreateVectorStoreForPublicScope();
       const createVectorStoreFileBatchResponse = await this.client.createVectorStoreFileBatch(vectorStore.vectorStoreId, uploadedFileIds);
       logger.debug('Create vector store file', createVectorStoreFileBatchResponse);
+
+      // Set isAttachedToVectorStore: true when the uploaded file is attached to VectorStore
+      await VectorStoreFileRelationModel.markAsAttachedToVectorStore(pageIds);
     }
     catch (err) {
       logger.error(err);
 
       // Delete all uploaded files if createVectorStoreFileBatch fails
-      const pageIds = pages.map(page => page._id);
       for await (const pageId of pageIds) {
         await this.deleteVectorStoreFile(pageId);
       }

+ 8 - 7
apps/app/src/features/questionnaire/server/routes/apiv3/questionnaire.ts

@@ -1,15 +1,17 @@
 import type { IUserHasId } from '@growi/core';
-import { Router, Request } from 'express';
+import type { Request } from 'express';
+import { Router } from 'express';
 import { body, validationResult } from 'express-validator';
 
-import Crowi from '~/server/crowi';
-import { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
+import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
+import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import axios from '~/utils/axios';
 import loggerFactory from '~/utils/logger';
 
-import { IAnswer } from '../../../interfaces/answer';
-import { IProactiveQuestionnaireAnswer } from '../../../interfaces/proactive-questionnaire-answer';
-import { IQuestionnaireAnswer } from '../../../interfaces/questionnaire-answer';
+import type { IAnswer } from '../../../interfaces/answer';
+import type { IProactiveQuestionnaireAnswer } from '../../../interfaces/proactive-questionnaire-answer';
+import type { IQuestionnaireAnswer } from '../../../interfaces/questionnaire-answer';
 import { StatusType } from '../../../interfaces/questionnaire-answer-status';
 import ProactiveQuestionnaireAnswer from '../../models/proactive-questionnaire-answer';
 import QuestionnaireAnswer from '../../models/questionnaire-answer';
@@ -25,7 +27,6 @@ interface AuthorizedRequest extends Request {
 }
 
 module.exports = (crowi: Crowi): Router => {
-  const accessTokenParser = require('~/server/middlewares/access-token-parser')(crowi);
   const loginRequired = require('~/server/middlewares/login-required')(crowi, true);
 
   const validators = {

+ 1 - 1
apps/app/src/migrations/20180926134048-make-email-unique.js

@@ -11,7 +11,7 @@ module.exports = {
 
   async up(db, next) {
     logger.info('Start migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = userModelFactory();
 

+ 2 - 3
apps/app/src/migrations/20180927102719-init-serverurl.js

@@ -1,6 +1,5 @@
 import mongoose from 'mongoose';
 
-// eslint-disable-next-line import/no-named-as-default
 import { Config } from '~/server/models/config';
 import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
@@ -21,7 +20,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     // find 'app:siteUrl'
     const siteUrlConfig = await Config.findOne({
@@ -77,7 +76,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     // remote 'app:siteUrl'
     await Config.findOneAndDelete({

+ 2 - 2
apps/app/src/migrations/20181019114028-abolish-page-group-relation.js

@@ -31,7 +31,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const isPagegrouprelationsExists = await isCollectionExists(db, 'pagegrouprelations');
     if (!isPagegrouprelationsExists) {
@@ -75,7 +75,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = pageModelFactory();
     const UserGroup = userGroupModelFactory();

+ 1 - 1
apps/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:abolish-crowi-classic-auth');
 module.exports = {
   async up(db, next) {
     logger.info('Start migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     // enable passport and delete configs for crowi classic auth
     await Promise.all([

+ 2 - 2
apps/app/src/migrations/20190618104011-add-config-app-installed.js

@@ -19,7 +19,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = userModelFactory();
 
@@ -49,7 +49,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     // remote 'app:siteUrl'
     await Config.findOneAndDelete({

+ 1 - 1
apps/app/src/migrations/20190619055421-adjust-page-grant.js

@@ -10,7 +10,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = getPageModel();
 

+ 1 - 1
apps/app/src/migrations/20190624110950-fill-last-update-user.js

@@ -13,7 +13,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = getPageModel();
 

+ 1 - 1
apps/app/src/migrations/20190629193445-make-root-page-public.js

@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:make-root-page-public');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = getPageModel();
 

+ 1 - 1
apps/app/src/migrations/20191102223900-drop-configs-indices.js

@@ -14,7 +14,7 @@ async function dropIndexIfExists(collection, indexName) {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const collection = db.collection('configs');
     await dropIndexIfExists(collection, 'ns_1');

+ 1 - 1
apps/app/src/migrations/20191102223901-drop-pages-indices.js

@@ -21,7 +21,7 @@ async function dropIndexIfExists(db, collectionName, indexName) {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await dropIndexIfExists(db, 'pages', 'lastUpdateUser_1');
     await dropIndexIfExists(db, 'pages', 'liker_1');

+ 1 - 1
apps/app/src/migrations/20191126173016-adjust-pages-path.js

@@ -11,7 +11,7 @@ const logger = loggerFactory('growi:migrate:adjust-pages-path');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = getPageModel();
 

+ 1 - 1
apps/app/src/migrations/20191127023815-drop-wrong-index-of-page-tag-relation.js

@@ -21,7 +21,7 @@ async function dropIndexIfExists(db, collectionName, indexName) {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await dropIndexIfExists(db, 'pagetagrelations', 'page_1_user_1');
 

+ 1 - 1
apps/app/src/migrations/20200402160380-remove-deleteduser-from-relationgroup.js

@@ -11,7 +11,7 @@ const logger = loggerFactory('growi:migrate:remove-deleteduser-from-relationgrou
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = userModelFactory();
 

+ 1 - 1
apps/app/src/migrations/20200420160390-remove-crowi-layout.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const query = { key: 'customize:layout', value: JSON.stringify('crowi') };
 

+ 2 - 2
apps/app/src/migrations/20200512005851-remove-behavior-type.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:remove-behavior-type');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.findOneAndDelete({ key: 'customize:behavior' }); // remove behavior
 
@@ -20,7 +20,7 @@ module.exports = {
   async down(db, client) {
     // do not rollback
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const insertConfig = new Config({
       ns: 'crowi',

+ 1 - 1
apps/app/src/migrations/20200514001356-update-theme-color-for-dark.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:update-theme-color-for-dark');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await Promise.all([
       await Config.findOneAndUpdate({ key: 'customize:theme', value: JSON.stringify('default-dark') }, { value: JSON.stringify('default') }), // update default-dark

+ 1 - 1
apps/app/src/migrations/20200620203632-normalize-locale-id.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:normalize-locale-id');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = userModelFactory();
 

+ 2 - 2
apps/app/src/migrations/20200827045151-remove-layout-setting.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:remove-layout-setting');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const layoutType = await Config.findOne({ key: 'customize:layout' });
 
@@ -39,7 +39,7 @@ module.exports = {
 
   async down(db, client) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const theme = await Config.findOne({ key: 'customize:theme' });
     const insertLayoutType = (theme.value === '"kibela"') ? 'kibela' : 'growi';

+ 2 - 2
apps/app/src/migrations/20200828024025-copy-aws-setting.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:remove-layout-setting');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const [accessKeyId, secretAccessKey] = await Promise.all([
       Config.findOne({ key: 'aws:accessKeyId' }),
@@ -56,7 +56,7 @@ module.exports = {
 
   async down(db, client) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.deleteMany({ key: { $in: ['mail:sesAccessKeyId', 'mail:sesSecretAccessKey'] } });
 

+ 2 - 2
apps/app/src/migrations/20200901034313-update-mail-transmission.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:update-mail-transmission');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const sesAccessKeyId = await Config.findOne({
       ns: 'crowi',
@@ -43,7 +43,7 @@ module.exports = {
 
   async down(db, client) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     // remote 'mail:transmissionMethod'
     await Config.findOneAndDelete({

+ 2 - 2
apps/app/src/migrations/20200903080025-remove-timeline-type.js.js

@@ -11,7 +11,7 @@ const mongoose = require('mongoose');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.findOneAndDelete({ key: 'customize:isEnabledTimeline' }); // remove timeline
 
@@ -21,7 +21,7 @@ module.exports = {
   async down(db, client) {
     // do not rollback
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const insertConfig = new Config({
       ns: 'crowi',

+ 2 - 2
apps/app/src/migrations/20200915035234-rename-s3-config.js

@@ -34,7 +34,7 @@ const awsConfigs = [
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const request = awsConfigs.map((awsConfig) => {
       return {
@@ -53,7 +53,7 @@ module.exports = {
   async down(db, client) {
     logger.info('Rollback migration');
 
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const request = awsConfigs.map((awsConfig) => {
       return {

+ 1 - 1
apps/app/src/migrations/20210420160380-convert-double-to-date.js

@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = getModelSafely('Page') || getPageModel();
 

+ 2 - 2
apps/app/src/migrations/20210830074539-update-configs-for-slackbot.js

@@ -17,7 +17,7 @@ const keyMap = {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     for await (const [oldKey, newKey] of Object.entries(keyMap)) {
       const isExist = (await Config.count({ key: newKey })) > 0;
@@ -37,7 +37,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     for await (const [oldKey, newKey] of Object.entries(keyMap)) {
       const isExist = (await Config.count({ key: oldKey })) > 0;

+ 1 - 1
apps/app/src/migrations/20210906194521-slack-app-integration-set-default-value.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:slack-app-integration-set-default-va
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const SlackAppIntegration = slackAppIntegrationFactory();
 

+ 2 - 2
apps/app/src/migrations/20210921173042-add-is-trashed-field.js

@@ -21,7 +21,7 @@ const updateIsPageTrashed = async(db, updateIdList) => {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
     const Page = getModelSafely('Page') || getPageModel();
 
     let updateDeletedPageIds = [];
@@ -52,7 +52,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     try {
       await db.collection('pagetagrelations').updateMany(

+ 2 - 2
apps/app/src/migrations/20211005120030-slack-app-integration-rename-keys.js

@@ -8,7 +8,7 @@ const logger = loggerFactory('growi:migrate:slack-app-integration-rename-keys');
 
 module.exports = {
   async up(db) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const SlackAppIntegration = slackAppIntegrationFactory();
 
@@ -45,7 +45,7 @@ module.exports = {
   },
 
   async down(db, next) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const SlackAppIntegration = slackAppIntegrationFactory();
 

+ 2 - 2
apps/app/src/migrations/20211005131430-config-without-proxy-command-permission-for-renaming.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:slack-app-integration-rename-keys');
 
 module.exports = {
   async up(db) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const isExist = (await Config.count({ key: 'slackbot:withoutProxy:commandPermission' })) > 0;
     if (!isExist) return;
@@ -53,7 +53,7 @@ module.exports = {
   },
 
   async down(db, next) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const isExist = (await Config.count({ key: 'slackbot:withoutProxy:commandPermission' })) > 0;
     if (!isExist) return next();

+ 2 - 2
apps/app/src/migrations/20211129125654-initialize-private-legacy-pages-named-query.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:initialize-private-legacy-pages-name
 
 module.exports = {
   async up(db, next) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     try {
       await NamedQuery.updateOne(
@@ -29,7 +29,7 @@ module.exports = {
   },
 
   async down(db, next) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     try {
       await NamedQuery.findOneAndDelete({

+ 2 - 2
apps/app/src/migrations/20211227060705-revision-path-to-page-id-schema-migration--fixed-7549.js

@@ -17,7 +17,7 @@ const LIMIT = 300;
 module.exports = {
   // path => pageId
   async up(db, client) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
     const Page = getModelSafely('Page') || getPageModel();
 
     const pagesStream = await Page.find({ revision: { $ne: null } }, { _id: 1, path: 1 }).cursor({ batch_size: LIMIT });
@@ -67,7 +67,7 @@ module.exports = {
 
   // pageId => path
   async down(db, client) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
     const Page = getModelSafely('Page') || getPageModel();
 
     const pagesStream = await Page.find({ revision: { $ne: null } }, { _id: 1, path: 1 }).cursor({ batch_size: LIMIT });

+ 2 - 2
apps/app/src/migrations/20220131001218-convert-redirect-to-pages-to-page-redirect-documents.js

@@ -13,7 +13,7 @@ const BATCH_SIZE = 100;
 
 module.exports = {
   async up(db, client) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
     const pageCollection = await db.collection('pages');
     const PageRedirect = getModelSafely('PageRedirect') || PageRedirectModel;
 
@@ -49,7 +49,7 @@ module.exports = {
   },
 
   async down(db, client) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
     const pageCollection = await db.collection('pages');
     const PageRedirect = getModelSafely('PageRedirect') || PageRedirectModel;
 

+ 1 - 1
apps/app/src/migrations/20220311011114-convert-page-delete-config.js

@@ -12,7 +12,7 @@ const logger = loggerFactory('growi:migrate:convert-page-delete-config');
 
 module.exports = {
   async up(db, client) {
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const isNewConfigExists = await Config.count({
       ns: 'crowi',

+ 1 - 1
apps/app/src/migrations/20220411114257-set-sparse-option-to-slack-member-id.js

@@ -12,7 +12,7 @@ const logger = loggerFactory('growi:migrate:set-sparse-option-to-slack-member-id
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = userModelFactory();
     await User.syncIndexes();

+ 1 - 1
apps/app/src/migrations/20220613064207-add-attachment-type-to-existing-attachments.js

@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:add-attachment-type-to-existing-atta
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     // Add attachmentType for wiki page
     // Filter pages where "attachmentType" doesn't exist and "page" is not null

+ 2 - 2
apps/app/src/migrations/20221014130200-remove-customize-is-saved-states-of-tab-changes.js

@@ -11,7 +11,7 @@ const mongoose = require('mongoose');
 module.exports = {
   async up() {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.findOneAndDelete({ key: 'customize:isSavedStatesOfTabChanges' }); // remove isSavedStatesOfTabChanges
 
@@ -20,7 +20,7 @@ module.exports = {
 
   async down() {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const insertConfig = new Config({
       ns: 'crowi',

+ 1 - 1
apps/app/src/migrations/20221219011829-remove-basic-auth-related-config.js

@@ -11,7 +11,7 @@ const mongoose = require('mongoose');
 module.exports = {
   async up() {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.findOneAndDelete({ key: 'security:passport-basic:isEnabled' });
     await Config.findOneAndDelete({ key: 'security:passport-basic:isSameUsernameTreatedAsIdenticalUser' });

+ 2 - 2
apps/app/src/migrations/20230213090921-remove-presentation-configurations.js

@@ -11,7 +11,7 @@ const mongoose = require('mongoose');
 module.exports = {
   async up() {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.findOneAndDelete({ key: 'markdown:presentation:pageBreakSeparator' });
     await Config.findOneAndDelete({ key: 'markdown:presentation:pageBreakCustomSeparator' });
@@ -21,7 +21,7 @@ module.exports = {
 
   async down() {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const insertConfig = new Config({
       ns: 'crowi',

+ 2 - 2
apps/app/src/migrations/20230731075753-add_installed_date_to_config.js

@@ -10,7 +10,7 @@ const mongoose = require('mongoose');
 module.exports = {
   async up() {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
     const User = userModelFactory();
 
     const appInstalled = await Config.findOne({ key: 'app:installed' });
@@ -36,7 +36,7 @@ module.exports = {
 
   async down() {
     logger.info('Rollback migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const appInstalled = await Config.findOne({ key: 'app:installed' });
     if (appInstalled != null) {

+ 1 - 1
apps/app/src/migrations/20231102012742-clean-user-ui-settings-collection.js

@@ -11,7 +11,7 @@ const mongoose = require('mongoose');
 module.exports = {
   async up() {
     logger.info('Apply migration');
-    mongoose.connect(getMongoUri(), mongoOptions);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     await UserUISettings.updateMany(
       {},

+ 9 - 0
apps/app/src/server/crowi/index.js

@@ -23,6 +23,7 @@ import loggerFactory from '~/utils/logger';
 import { projectRoot } from '~/utils/project-dir-utils';
 
 import UserEvent from '../events/user';
+import { accessTokenParser } from '../middlewares/access-token-parser';
 import { aclService as aclServiceSingletonInstance } from '../service/acl';
 import AppService from '../service/app';
 import AttachmentService from '../service/attachment';
@@ -57,6 +58,12 @@ const sep = path.sep;
 
 class Crowi {
 
+  /**
+   * For retrieving other packages
+   * @type {(req: import('express').Request, res: import('express').Response, next: import('express').NextFunction) => Promise<void>}
+   */
+  accessTokenParser;
+
   /** @type {AppService} */
   appService;
 
@@ -85,6 +92,8 @@ class Crowi {
 
     this.express = null;
 
+    this.accessTokenParser = accessTokenParser;
+
     this.config = {};
     this.configManager = null;
     this.s2sMessagingService = null;

+ 0 - 37
apps/app/src/server/middlewares/access-token-parser.js

@@ -1,37 +0,0 @@
-import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
-import mongoose from 'mongoose';
-
-import loggerFactory from '~/utils/logger';
-
-
-const logger = loggerFactory('growi:middleware:access-token-parser');
-
-module.exports = (crowi) => {
-
-  return async(req, res, next) => {
-    // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
-    const accessToken = req.query.access_token || req.body.access_token || null;
-    if (accessToken == null || typeof accessToken !== 'string') {
-      return next();
-    }
-
-    const User = mongoose.model('User');
-
-    logger.debug('accessToken is', accessToken);
-
-    const user = await User.findUserByApiToken(accessToken).lean();
-
-    if (user == null) {
-      logger.debug('The access token is invalid');
-      return next();
-    }
-
-    // transforming attributes
-    req.user = serializeUserSecurely(user);
-
-    logger.debug('Access token parsed.');
-
-    return next();
-  };
-
-};

+ 135 - 0
apps/app/src/server/middlewares/access-token-parser/access-token-parser.integ.ts

@@ -0,0 +1,135 @@
+import { faker } from '@faker-js/faker';
+import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
+import type { Response } from 'express';
+import { mock } from 'vitest-mock-extended';
+
+import type Crowi from '~/server/crowi';
+import type UserEvent from '~/server/events/user';
+
+
+import type { AccessTokenParserReq } from './interfaces';
+
+import { accessTokenParser } from '.';
+
+
+vi.mock('@growi/core/dist/models/serializers', { spy: true });
+
+
+describe('access-token-parser middleware', () => {
+
+  let User;
+
+  beforeAll(async() => {
+    const crowiMock = mock<Crowi>({
+      event: vi.fn().mockImplementation((eventName) => {
+        if (eventName === 'user') {
+          return mock<UserEvent>({
+            on: vi.fn(),
+          });
+        }
+      }),
+    });
+    const userModelFactory = (await import('../../models/user')).default;
+    User = userModelFactory(crowiMock);
+  });
+
+  it('should call next if no access token is provided', async() => {
+    // arrange
+    const reqMock = mock<AccessTokenParserReq>({
+      user: undefined,
+    });
+    const resMock = mock<Response>();
+    const nextMock = vi.fn();
+
+    expect(reqMock.user).toBeUndefined();
+
+    // act
+    await accessTokenParser(reqMock, resMock, nextMock);
+
+    // assert
+    expect(reqMock.user).toBeUndefined();
+    expect(serializeUserSecurely).not.toHaveBeenCalled();
+    expect(nextMock).toHaveBeenCalled();
+  });
+
+  it('should call next if the given access token is invalid', async() => {
+    // arrange
+    const reqMock = mock<AccessTokenParserReq>({
+      user: undefined,
+    });
+    const resMock = mock<Response>();
+    const nextMock = vi.fn();
+
+    expect(reqMock.user).toBeUndefined();
+
+    // act
+    reqMock.query.access_token = 'invalidToken';
+    await accessTokenParser(reqMock, resMock, nextMock);
+
+    // assert
+    expect(reqMock.user).toBeUndefined();
+    expect(serializeUserSecurely).not.toHaveBeenCalled();
+    expect(nextMock).toHaveBeenCalled();
+  });
+
+  it('should set req.user with a valid access token in query', async() => {
+    // arrange
+    const reqMock = mock<AccessTokenParserReq>({
+      user: undefined,
+    });
+    const resMock = mock<Response>();
+    const nextMock = vi.fn();
+
+    expect(reqMock.user).toBeUndefined();
+
+    // prepare a user with an access token
+    const targetUser = await User.create({
+      name: faker.person.fullName(),
+      username: faker.string.uuid(),
+      password: faker.internet.password(),
+      lang: 'en_US',
+      apiToken: faker.internet.password(),
+    });
+
+    // act
+    reqMock.query.access_token = targetUser.apiToken;
+    await accessTokenParser(reqMock, resMock, nextMock);
+
+    // assert
+    expect(reqMock.user).toBeDefined();
+    expect(reqMock.user?._id).toStrictEqual(targetUser._id);
+    expect(serializeUserSecurely).toHaveBeenCalledOnce();
+    expect(nextMock).toHaveBeenCalled();
+  });
+
+  it('should set req.user with a valid access token in body', async() => {
+    // arrange
+    const reqMock = mock<AccessTokenParserReq>({
+      user: undefined,
+    });
+    const resMock = mock<Response>();
+    const nextMock = vi.fn();
+
+    expect(reqMock.user).toBeUndefined();
+
+    // prepare a user with an access token
+    const targetUser = await User.create({
+      name: faker.person.fullName(),
+      username: faker.string.uuid(),
+      password: faker.internet.password(),
+      lang: 'en_US',
+      apiToken: faker.internet.password(),
+    });
+
+    // act
+    reqMock.body.access_token = targetUser.apiToken;
+    await accessTokenParser(reqMock, resMock, nextMock);
+
+    // assert
+    expect(reqMock.user).toBeDefined();
+    expect(reqMock.user?._id).toStrictEqual(targetUser._id);
+    expect(serializeUserSecurely).toHaveBeenCalledOnce();
+    expect(nextMock).toHaveBeenCalled();
+  });
+
+});

+ 38 - 0
apps/app/src/server/middlewares/access-token-parser/access-token-parser.ts

@@ -0,0 +1,38 @@
+import type { IUser, IUserHasId } from '@growi/core/dist/interfaces';
+import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
+import type { NextFunction, Response } from 'express';
+import type { HydratedDocument } from 'mongoose';
+import mongoose from 'mongoose';
+
+import loggerFactory from '~/utils/logger';
+
+import type { AccessTokenParserReq } from './interfaces';
+
+const logger = loggerFactory('growi:middleware:access-token-parser');
+
+
+export const accessTokenParser = async(req: AccessTokenParserReq, res: Response, next: NextFunction): Promise<void> => {
+  // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
+  const accessToken = req.query.access_token ?? req.body.access_token;
+  if (accessToken == null || typeof accessToken !== 'string') {
+    return next();
+  }
+
+  const User = mongoose.model<HydratedDocument<IUser>, { findUserByApiToken }>('User');
+
+  logger.debug('accessToken is', accessToken);
+
+  const user: IUserHasId = await User.findUserByApiToken(accessToken);
+
+  if (user == null) {
+    logger.debug('The access token is invalid');
+    return next();
+  }
+
+  // transforming attributes
+  req.user = serializeUserSecurely(user);
+
+  logger.debug('Access token parsed.');
+
+  return next();
+};

+ 1 - 0
apps/app/src/server/middlewares/access-token-parser/index.ts

@@ -0,0 +1 @@
+export * from './access-token-parser';

+ 14 - 0
apps/app/src/server/middlewares/access-token-parser/interfaces.ts

@@ -0,0 +1,14 @@
+import type { IUserHasId } from '@growi/core/dist/interfaces';
+import type { IUserSerializedSecurely } from '@growi/core/dist/models/serializers';
+import type { Request } from 'express';
+
+type ReqQuery = {
+  access_token?: string,
+}
+type ReqBody = {
+  access_token?: string,
+}
+
+export interface AccessTokenParserReq extends Request<undefined, undefined, ReqBody, ReqQuery> {
+  user?: IUserSerializedSecurely<IUserHasId>,
+}

+ 5 - 11
apps/app/src/server/models/user.js

@@ -7,6 +7,8 @@ import { i18n } from '^/config/next-i18next.config';
 import { generateGravatarSrc } from '~/utils/gravatar';
 import loggerFactory from '~/utils/logger';
 
+import { aclService } from '../service/acl';
+import { configManager } from '../service/config-manager';
 import { getModelSafely } from '../util/mongoose-utils';
 
 import { Attachment } from './attachment';
@@ -99,8 +101,6 @@ const factory = (crowi) => {
   function decideUserStatusOnRegistration() {
     validateCrowi();
 
-    const { configManager, aclService } = crowi;
-
     const isInstalled = configManager.getConfig('crowi', 'app:installed');
     if (!isInstalled) {
       return STATUS_ACTIVE; // is this ok?
@@ -278,7 +278,7 @@ const factory = (crowi) => {
     this.name = name;
     this.username = username;
     this.status = STATUS_ACTIVE;
-    this.isEmailPublished = crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser');
+    this.isEmailPublished = configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser');
 
     this.save((err, userData) => {
       userEvent.emit('activated', userData);
@@ -371,7 +371,7 @@ const factory = (crowi) => {
   userSchema.statics.isEmailValid = function(email, callback) {
     validateCrowi();
 
-    const whitelist = crowi.configManager.getConfig('crowi', 'security:registrationWhitelist');
+    const whitelist = configManager.getConfig('crowi', 'security:registrationWhitelist');
 
     if (Array.isArray(whitelist) && whitelist.length > 0) {
       return whitelist.some((allowedEmail) => {
@@ -449,7 +449,7 @@ const factory = (crowi) => {
     if (apiToken == null) {
       return Promise.resolve(null);
     }
-    return this.findOne({ apiToken });
+    return this.findOne({ apiToken }).lean();
   };
 
   userSchema.statics.findUserByGoogleId = function(googleId, callback) {
@@ -480,8 +480,6 @@ const factory = (crowi) => {
   };
 
   userSchema.statics.isUserCountExceedsUpperLimit = async function() {
-    const { configManager } = crowi;
-
     const userUpperLimit = configManager.getConfig('crowi', 'security:userUpperLimit');
 
     const activeUsers = await this.countActiveUsers();
@@ -565,8 +563,6 @@ const factory = (crowi) => {
   };
 
   userSchema.statics.createUserByEmail = async function(email) {
-    const configManager = crowi.configManager;
-
     const User = this;
     const newUser = new User();
 
@@ -654,8 +650,6 @@ const factory = (crowi) => {
       newUser.setPassword(password);
     }
 
-    const configManager = crowi.configManager;
-
     // Default email show/hide is up to the administrator
     newUser.isEmailPublished = configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser');
 

+ 1 - 1
apps/app/src/server/routes/apiv3/activity.ts

@@ -5,6 +5,7 @@ import express from 'express';
 import { query } from 'express-validator';
 
 import type { IActivity, ISearchFilter } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import Activity from '~/server/models/activity';
 import loggerFactory from '~/utils/logger';
 
@@ -27,7 +28,6 @@ const validator = {
 
 module.exports = (crowi: Crowi): Router => {
   const adminRequired = require('../../middlewares/admin-required')(crowi);
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
 
   const router = express.Router();

+ 1 - 1
apps/app/src/server/routes/apiv3/app-settings.js

@@ -4,6 +4,7 @@ import { body } from 'express-validator';
 import { i18n } from '^/config/next-i18next.config';
 
 import { SupportedAction } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import loggerFactory from '~/utils/logger';
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -143,7 +144,6 @@ const router = express.Router();
  */
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/attachment.js

@@ -6,6 +6,7 @@ import autoReap from 'multer-autoreap';
 
 import { SupportedAction } from '~/interfaces/activity';
 import { AttachmentType } from '~/server/interfaces/attachment';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Attachment } from '~/server/models/attachment';
 import { serializePageSecurely, serializeRevisionSecurely } from '~/server/models/serializers';
 import loggerFactory from '~/utils/logger';
@@ -82,7 +83,6 @@ const {
  */
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const Page = crowi.model('Page');

+ 1 - 1
apps/app/src/server/routes/apiv3/bookmark-folder.ts

@@ -3,6 +3,7 @@ import { body } from 'express-validator';
 import type { Types } from 'mongoose';
 
 import type { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { InvalidParentBookmarkFolderError } from '~/server/models/errors';
 import { serializeBookmarkSecurely } from '~/server/models/serializers/bookmark-serializer';
@@ -39,7 +40,6 @@ const validator = {
 };
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
 
   // Create new bookmark folder

+ 1 - 1
apps/app/src/server/routes/apiv3/bookmarks.js

@@ -1,6 +1,7 @@
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { serializeBookmarkSecurely } from '~/server/models/serializers/bookmark-serializer';
 import { preNotifyService } from '~/server/service/pre-notify';
@@ -68,7 +69,6 @@ const router = express.Router();
  */
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const addActivity = generateAddActivityMiddleware(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/export.js

@@ -1,5 +1,6 @@
 import { SupportedAction } from '~/interfaces/activity';
 import { exportService } from '~/server/service/export';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import loggerFactory from '~/utils/logger';
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -39,7 +40,6 @@ const router = express.Router();
  */
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/g2g-transfer.ts

@@ -7,6 +7,7 @@ import express from 'express';
 import { body } from 'express-validator';
 import multer from 'multer';
 
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { isG2GTransferError } from '~/server/models/vo/g2g-transfer-error';
 import { configManager } from '~/server/service/config-manager';
 import { exportService } from '~/server/service/export';
@@ -84,7 +85,6 @@ module.exports = (crowi: Crowi): Router => {
 
   const isInstalled = crowi.configManager?.getConfig('crowi', 'app:installed');
 
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
 

+ 1 - 1
apps/app/src/server/routes/apiv3/import.js

@@ -1,6 +1,7 @@
 import { ErrorV3 } from '@growi/core/dist/models';
 
 import { SupportedAction } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { getImportService } from '~/server/service/import';
 import { generateOverwriteParams } from '~/server/service/import/overwrite-params';
 import loggerFactory from '~/utils/logger';
@@ -58,7 +59,6 @@ export default function route(crowi) {
   const { growiBridgeService, socketIoService } = crowi;
   const importService = getImportService(crowi);
 
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/in-app-notification.ts

@@ -3,6 +3,7 @@ import express from 'express';
 
 import { SupportedAction } from '~/interfaces/activity';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 
 import type { IInAppNotification } from '../../../interfaces/in-app-notification';
@@ -14,7 +15,6 @@ const router = express.Router();
 
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 

+ 1 - 1
apps/app/src/server/routes/apiv3/page-listing.ts

@@ -9,6 +9,7 @@ import { query, oneOf } from 'express-validator';
 import type { HydratedDocument } from 'mongoose';
 import mongoose from 'mongoose';
 
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { configManager } from '~/server/service/config-manager';
 import type { IPageGrantService } from '~/server/service/page-grant';
 import loggerFactory from '~/utils/logger';
@@ -60,7 +61,6 @@ const validator = {
  * Routes
  */
 const routerFactory = (crowi: Crowi): Router => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
 
   const router = express.Router();

+ 2 - 2
apps/app/src/server/routes/apiv3/page/check-page-existence.ts

@@ -7,10 +7,11 @@ import { query } from 'express-validator';
 import mongoose from 'mongoose';
 
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
+import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import type { PageModel } from '~/server/models/page';
 import loggerFactory from '~/utils/logger';
 
-import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
 import type { ApiV3Response } from '../interfaces/apiv3-response';
 
 
@@ -30,7 +31,6 @@ type CreatePageHandlersFactory = (crowi: Crowi) => RequestHandler[];
 export const checkPageExistenceHandlersFactory: CreatePageHandlersFactory = (crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
 
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../../middlewares/login-required')(crowi, true);
 
   // define validators for req.body

+ 1 - 1
apps/app/src/server/routes/apiv3/page/create-page.ts

@@ -17,6 +17,7 @@ import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
 import { subscribeRuleNames } from '~/interfaces/in-app-notification';
 import type { IOptionsForCreate } from '~/interfaces/page';
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { GlobalNotificationSettingEvent } from '~/server/models/GlobalNotificationSetting';
 import type { PageDocument, PageModel } from '~/server/models/page';
@@ -101,7 +102,6 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
   const User = mongoose.model<IUser, { isExistUserByUserPagePath: any }>('User');
 
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
 
 

+ 1 - 1
apps/app/src/server/routes/apiv3/page/get-yjs-data.ts

@@ -6,6 +6,7 @@ import { param } from 'express-validator';
 import mongoose from 'mongoose';
 
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import loggerFactory from '~/utils/logger';
 
@@ -25,7 +26,6 @@ interface Req extends Request<ReqParams, ApiV3Response> {
 }
 export const getYjsDataHandlerFactory: GetYjsDataHandlerFactory = (crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
 
   // define validators for req.params

+ 1 - 1
apps/app/src/server/routes/apiv3/page/index.ts

@@ -14,6 +14,7 @@ import sanitize from 'sanitize-filename';
 
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import type { IPageGrantData } from '~/interfaces/page';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { excludeReadOnlyUser } from '~/server/middlewares/exclude-read-only-user';
@@ -108,7 +109,6 @@ const router = express.Router();
  *            example: 5e07345972560e001761fa63
  */
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../../middlewares/login-required')(crowi, true);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
   const certifySharedPage = require('../../../middlewares/certify-shared-page')(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/page/publish-page.ts

@@ -6,6 +6,7 @@ import { param } from 'express-validator';
 import mongoose from 'mongoose';
 
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import loggerFactory from '~/utils/logger';
 
@@ -29,7 +30,6 @@ type PublishPageHandlersFactory = (crowi: Crowi) => RequestHandler[];
 export const publishPageHandlersFactory: PublishPageHandlersFactory = (crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
 
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
 
   // define validators for req.body

+ 1 - 1
apps/app/src/server/routes/apiv3/page/sync-latest-revision-body-to-yjs-draft.ts

@@ -6,6 +6,7 @@ import { param, body } from 'express-validator';
 import mongoose from 'mongoose';
 
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import { getYjsService } from '~/server/service/yjs';
 import loggerFactory from '~/utils/logger';
@@ -29,7 +30,6 @@ interface Req extends Request<ReqParams, ApiV3Response, ReqBody> {
 }
 export const syncLatestRevisionBodyToYjsDraftHandlerFactory: SyncLatestRevisionBodyToYjsDraftHandlerFactory = (crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
 
   // define validators for req.params

+ 1 - 1
apps/app/src/server/routes/apiv3/page/unpublish-page.ts

@@ -6,6 +6,7 @@ import { param } from 'express-validator';
 import mongoose from 'mongoose';
 
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import type { PageModel } from '~/server/models/page';
 import loggerFactory from '~/utils/logger';
 
@@ -29,7 +30,6 @@ type UnpublishPageHandlersFactory = (crowi: Crowi) => RequestHandler[];
 export const unpublishPageHandlersFactory: UnpublishPageHandlersFactory = (crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
 
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
 
   // define validators for req.body

+ 1 - 1
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -16,6 +16,7 @@ import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
 import { type IApiv3PageUpdateParams, PageUpdateErrorCode } from '~/interfaces/apiv3';
 import type { IOptionsForUpdate } from '~/interfaces/page';
 import type Crowi from '~/server/crowi';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import { GlobalNotificationSettingEvent } from '~/server/models/GlobalNotificationSetting';
 import type { PageDocument, PageModel } from '~/server/models/page';
@@ -46,7 +47,6 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
   const Revision = mongoose.model<IRevisionHasId>('Revision');
 
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
 
   // define validators for req.body

+ 1 - 1
apps/app/src/server/routes/apiv3/pages/index.js

@@ -9,6 +9,7 @@ import { body, query } from 'express-validator';
 
 import { SupportedTargetModel, SupportedAction } from '~/interfaces/activity';
 import { subscribeRuleNames } from '~/interfaces/in-app-notification';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { GlobalNotificationSettingEvent } from '~/server/models/GlobalNotificationSetting';
 import PageTagRelation from '~/server/models/page-tag-relation';
 import { preNotifyService } from '~/server/service/pre-notify';
@@ -58,7 +59,6 @@ const LIMIT_FOR_MULTIPLE_PAGE_OP = 20;
  */
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../../middlewares/login-required')(crowi, true);
   const loginRequiredStrictly = require('../../../middlewares/login-required')(crowi);
   const adminRequired = require('../../../middlewares/admin-required')(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/personal-setting.js

@@ -5,6 +5,7 @@ import { body } from 'express-validator';
 import { i18n } from '^/config/next-i18next.config';
 
 import { SupportedAction } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import loggerFactory from '~/utils/logger';
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -66,7 +67,6 @@ const router = express.Router();
  *            type: string
  */
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);
 

+ 1 - 1
apps/app/src/server/routes/apiv3/revisions.js

@@ -3,6 +3,7 @@ import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 import express from 'express';
 import { connection } from 'mongoose';
 
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { Revision } from '~/server/models/revision';
 import { normalizeLatestRevisionIfBroken } from '~/server/service/revision/normalize-latest-revision-if-broken';
 import loggerFactory from '~/utils/logger';
@@ -19,7 +20,6 @@ const MIGRATION_FILE_NAME = '20211227060705-revision-path-to-page-id-schema-migr
 
 module.exports = (crowi) => {
   const certifySharedPage = require('../../middlewares/certify-shared-page')(crowi);
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
 
   const {

+ 1 - 1
apps/app/src/server/routes/apiv3/search.js

@@ -1,6 +1,7 @@
 import { ErrorV3 } from '@growi/core/dist/models';
 
 import { SupportedAction } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import loggerFactory from '~/utils/logger';
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -17,7 +18,6 @@ const router = express.Router();
 const noCache = require('nocache');
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/slack-integration-settings.js

@@ -9,6 +9,7 @@ import {
 } from '@growi/slack/dist/utils/check-communicable';
 
 import { SupportedAction } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import loggerFactory from '~/utils/logger';
 
 import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
@@ -45,7 +46,6 @@ const router = express.Router();
 
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);
   const addActivity = generateAddActivityMiddleware(crowi);

+ 1 - 1
apps/app/src/server/routes/apiv3/users.js

@@ -10,6 +10,7 @@ import { isEmail } from 'validator';
 
 import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
 import { SupportedAction } from '~/interfaces/activity';
+import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import Activity from '~/server/models/activity';
 import ExternalAccount from '~/server/models/external-account';
 import { serializePageSecurely } from '~/server/models/serializers';
@@ -74,7 +75,6 @@ const validator = {};
  */
 
 module.exports = (crowi) => {
-  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const adminRequired = require('../../middlewares/admin-required')(crowi);

Некоторые файлы не были показаны из-за большого количества измененных файлов