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

Merge branch 'support/apply-nextjs-2' into support/apply-nextjs-to-PageComments

jam411 3 лет назад
Родитель
Сommit
3dcfb53398
100 измененных файлов с 506 добавлено и 412 удалено
  1. 10 0
      .eslintrc.js
  2. 38 33
      .github/workflows/ci-app-prod.yml
  3. 5 4
      .github/workflows/ci-app.yml
  4. 6 2
      .github/workflows/reusable-app-prod.yml
  5. 2 2
      package.json
  6. 4 0
      packages/app/.eslintignore
  7. 11 1
      packages/app/.eslintrc.js
  8. 4 1
      packages/app/.gitignore
  9. 0 0
      packages/app/_obsolete/config/webpack.common.js
  10. 0 0
      packages/app/_obsolete/config/webpack.dev.dll.js
  11. 0 0
      packages/app/_obsolete/config/webpack.dev.js
  12. 0 0
      packages/app/_obsolete/config/webpack.prod.js
  13. 0 0
      packages/app/_obsolete/src/client/admin.jsx
  14. 1 1
      packages/app/_obsolete/src/client/app.jsx
  15. 0 0
      packages/app/_obsolete/src/client/base.jsx
  16. 0 0
      packages/app/_obsolete/src/client/installer.jsx
  17. 0 0
      packages/app/_obsolete/src/client/nologin.jsx
  18. 6 1
      packages/app/config/migrate-mongo-config.js
  19. 0 0
      packages/app/config/next-i18next.config.ts
  20. 1 1
      packages/app/docker/Dockerfile
  21. 92 70
      packages/app/next.config.js
  22. 16 14
      packages/app/package.json
  23. 3 0
      packages/app/public/static/locales/en_US/admin.json
  24. 1 0
      packages/app/public/static/locales/en_US/translation.json
  25. 3 0
      packages/app/public/static/locales/ja_JP/admin.json
  26. 1 0
      packages/app/public/static/locales/ja_JP/translation.json
  27. 3 0
      packages/app/public/static/locales/zh_CN/admin.json
  28. 1 0
      packages/app/public/static/locales/zh_CN/translation.json
  29. 3 0
      packages/app/src/client/interfaces/clearable.ts
  30. 1 1
      packages/app/src/client/util/apiv3-client.ts
  31. 1 1
      packages/app/src/client/util/i18n.js
  32. 2 1
      packages/app/src/components/Admin/App/AppSetting.jsx
  33. 11 2
      packages/app/src/components/Admin/AuditLog/ActivityTable.tsx
  34. 18 3
      packages/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx
  35. 32 8
      packages/app/src/components/Admin/AuditLogManagement.tsx
  36. 1 1
      packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  37. 1 1
      packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  38. 6 4
      packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx
  39. 3 1
      packages/app/src/components/CustomNavigation/CustomNav.jsx
  40. 8 6
      packages/app/src/components/CustomNavigation/CustomNav.module.scss
  41. 12 18
      packages/app/src/components/CustomNavigation/CustomNavAndContents.tsx
  42. 1 1
      packages/app/src/components/DescendantsPageListModal.tsx
  43. 10 3
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  44. 3 5
      packages/app/src/components/NotFoundPage.tsx
  45. 1 1
      packages/app/src/components/Page/DisplaySwitcher.tsx
  46. 5 1
      packages/app/src/components/PageAlert/FixPageGrantAlert.tsx
  47. 0 136
      packages/app/src/components/PageTimeline.jsx
  48. 78 0
      packages/app/src/components/PageTimeline.tsx
  49. 1 1
      packages/app/src/components/ShareLink/ShareLink.jsx
  50. 2 0
      packages/app/src/interfaces/activity.ts
  51. 3 4
      packages/app/src/interfaces/in-app-notification.ts
  52. 1 1
      packages/app/src/migrations/20180926134048-make-email-unique.js
  53. 1 1
      packages/app/src/migrations/20180927102719-init-serverurl.js
  54. 2 2
      packages/app/src/migrations/20181019114028-abolish-page-group-relation.js
  55. 1 1
      packages/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js
  56. 1 1
      packages/app/src/migrations/20190618104011-add-config-app-installed.js
  57. 2 2
      packages/app/src/migrations/20190619055421-adjust-page-grant.js
  58. 2 2
      packages/app/src/migrations/20190624110950-fill-last-update-user.js
  59. 2 2
      packages/app/src/migrations/20190629193445-make-root-page-public.js
  60. 1 1
      packages/app/src/migrations/20191102223900-drop-configs-indices.js
  61. 1 1
      packages/app/src/migrations/20191102223901-drop-pages-indices.js
  62. 3 2
      packages/app/src/migrations/20191126173016-adjust-pages-path.js
  63. 1 1
      packages/app/src/migrations/20191127023815-drop-wrong-index-of-page-tag-relation.js
  64. 1 1
      packages/app/src/migrations/20200402160380-remove-deleteduser-from-relationgroup.js
  65. 1 1
      packages/app/src/migrations/20200420160390-remove-crowi-layout.js
  66. 1 1
      packages/app/src/migrations/20200512005851-remove-behavior-type.js
  67. 1 1
      packages/app/src/migrations/20200514001356-update-theme-color-for-dark.js
  68. 1 1
      packages/app/src/migrations/20200620203632-normalize-locale-id.js
  69. 1 1
      packages/app/src/migrations/20200827045151-remove-layout-setting.js
  70. 1 1
      packages/app/src/migrations/20200828024025-copy-aws-setting.js
  71. 1 1
      packages/app/src/migrations/20200901034313-update-mail-transmission.js
  72. 1 2
      packages/app/src/migrations/20200903080025-remove-timeline-type.js.js
  73. 1 2
      packages/app/src/migrations/20200915035234-rename-s3-config.js
  74. 2 2
      packages/app/src/migrations/20210420160380-convert-double-to-date.js
  75. 1 1
      packages/app/src/migrations/20210830074539-update-configs-for-slackbot.js
  76. 1 1
      packages/app/src/migrations/20210906194521-slack-app-integration-set-default-value.js
  77. 2 2
      packages/app/src/migrations/20210913153942-migrate-slack-app-integration-schema.js
  78. 2 2
      packages/app/src/migrations/20210921173042-add-is-trashed-field.js
  79. 1 1
      packages/app/src/migrations/20211005120030-slack-app-integration-rename-keys.js
  80. 1 1
      packages/app/src/migrations/20211005131430-config-without-proxy-command-permission-for-renaming.js
  81. 2 2
      packages/app/src/migrations/20211129125654-initialize-private-legacy-pages-named-query.js
  82. 4 3
      packages/app/src/migrations/20211227060705-revision-path-to-page-id-schema-migration.js
  83. 1 1
      packages/app/src/migrations/20220131001218-convert-redirect-to-pages-to-page-redirect-documents.js
  84. 2 3
      packages/app/src/migrations/20220311011114-convert-page-delete-config.js
  85. 1 1
      packages/app/src/migrations/20220411114257-set-sparse-option-to-slack-member-id.js
  86. 1 1
      packages/app/src/migrations/20220613064207-add-attachment-type-to-existing-attachments.js
  87. 2 1
      packages/app/src/pages/_app.page.tsx
  88. 2 1
      packages/app/src/pages/utils/commons.ts
  89. 13 4
      packages/app/src/pages/utils/objectid-transformer.ts
  90. 4 2
      packages/app/src/server/console.js
  91. 2 1
      packages/app/src/server/crowi/dev.js
  92. 2 1
      packages/app/src/server/crowi/express-init.js
  93. 1 1
      packages/app/src/server/crowi/index.js
  94. 2 13
      packages/app/src/server/models/activity.ts
  95. 2 1
      packages/app/src/server/models/config.ts
  96. 2 1
      packages/app/src/server/models/editor-settings.ts
  97. 6 3
      packages/app/src/server/models/in-app-notification-settings.ts
  98. 2 1
      packages/app/src/server/models/in-app-notification.ts
  99. 4 2
      packages/app/src/server/models/named-query.ts
  100. 2 1
      packages/app/src/server/models/page-operation.ts

+ 10 - 0
.eslintrc.js

@@ -56,6 +56,7 @@ module.exports = {
       },
       },
     ],
     ],
     '@typescript-eslint/no-explicit-any': 'off',
     '@typescript-eslint/no-explicit-any': 'off',
+    '@typescript-eslint/explicit-module-boundary-types': 'off',
     indent: [
     indent: [
       'error',
       'error',
       2,
       2,
@@ -80,4 +81,13 @@ module.exports = {
       },
       },
     ]],
     ]],
   },
   },
+  overrides: [
+    {
+      // enable the rule specifically for TypeScript files
+      files: ['*.ts', '*.tsx'],
+      rules: {
+        '@typescript-eslint/explicit-module-boundary-types': ['error'],
+      },
+    },
+  ],
 };
 };

+ 38 - 33
.github/workflows/ci-app-prod.yml

@@ -3,7 +3,25 @@ name: Node CI for app production
 on:
 on:
   push:
   push:
     branches:
     branches:
-      - master
+      # - master
+      - support/apply-nextjs-2
+    paths:
+      - .github/workflows/ci-app-prod.yml
+      - .github/workflows/reusable-app-prod.yml
+      - .github/workflows/reusable-app-reg-suit.yml
+      - tsconfig.base.json
+      - yarn.lock
+      - packages/app/**
+      - '!packages/app/docker/**'
+      - packages/core/**
+      - packages/slack/**
+      - packages/ui/**
+      - packages/plugin-**
+  pull_request:
+    branches:
+      # - master
+      - support/apply-nextjs-2
+    types: [opened, reopened, synchronize]
     paths:
     paths:
       - .github/workflows/ci-app-prod.yml
       - .github/workflows/ci-app-prod.yml
       - .github/workflows/reusable-app-prod.yml
       - .github/workflows/reusable-app-prod.yml
@@ -16,27 +34,12 @@ on:
       - packages/slack/**
       - packages/slack/**
       - packages/ui/**
       - packages/ui/**
       - packages/plugin-**
       - packages/plugin-**
-  # pull_request:
-  #   branches:
-  #       - master
-  #   types: [opened, reopened, synchronize]
-  #   paths:
-  #     - .github/workflows/ci-app-prod.yml
-  #     - .github/workflows/reusable-app-prod.yml
-  #     - .github/workflows/reusable-app-reg-suit.yml
-  #     - tsconfig.base.json
-  #     - yarn.lock
-  #     - packages/app/**
-  #     - '!packages/app/docker/**'
-  #     - packages/core/**
-  #     - packages/slack/**
-  #     - packages/ui/**
-  #     - packages/plugin-**
 
 
 jobs:
 jobs:
 
 
   test-prod-node14:
   test-prod-node14:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+    # uses: weseek/growi/.github/workflows/reusable-app-prod.yml@support/master
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@support/apply-nextjs-2
     with:
     with:
       node-version: 14.x
       node-version: 14.x
       skip-cypress: true
       skip-cypress: true
@@ -45,28 +48,30 @@ jobs:
 
 
 
 
   test-prod-node16:
   test-prod-node16:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+    # uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@support/apply-nextjs-2
     with:
     with:
       node-version: 16.x
       node-version: 16.x
-      skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+      # skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+      skip-cypress: true
       cypress-report-artifact-name: Cypress report
       cypress-report-artifact-name: Cypress report
     secrets:
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
 
 
-  run-reg-suit-node16:
-    needs: [test-prod-node16]
+  # run-reg-suit-node16:
+  #   needs: [test-prod-node16]
 
 
-    uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
+  #   uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
 
 
-    if: always()
+  #   if: always()
 
 
-    with:
-      node-version: 16.x
-      skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
-      cypress-report-artifact-name: Cypress report
-    secrets:
-      REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
-      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
-      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
-      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+  #   with:
+  #     node-version: 16.x
+  #     skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+  #     cypress-report-artifact-name: Cypress report
+  #   secrets:
+  #     REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
+  #     AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
+  #     AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+  #     SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

+ 5 - 4
.github/workflows/ci-app.yml

@@ -150,16 +150,17 @@ jobs:
           cache: 'yarn'
           cache: 'yarn'
           cache-dependency-path: '**/yarn.lock'
           cache-dependency-path: '**/yarn.lock'
 
 
-      - name: Cache/Restore node_modules
+      - name: Cache/Restore node_modules and next cache files
         id: cache-dependencies
         id: cache-dependencies
         uses: actions/cache@v3
         uses: actions/cache@v3
         with:
         with:
           path: |
           path: |
             **/node_modules
             **/node_modules
-          key: node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('packages/app/package.json') }}
+            ${{ github.workspace }}/packages/app/.next/cache
+          key: dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('packages/app/package.json') }}
           restore-keys: |
           restore-keys: |
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-
+            dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
+            dev-${{ runner.OS }}-node${{ matrix.node-version }}-
 
 
       - name: lerna bootstrap
       - name: lerna bootstrap
         run: |
         run: |

+ 6 - 2
.github/workflows/reusable-app-prod.yml

@@ -49,6 +49,7 @@ jobs:
     - name: Remove unnecessary packages
     - name: Remove unnecessary packages
       run: |
       run: |
         rm -rf packages/slackbot-proxy
         rm -rf packages/slackbot-proxy
+        rm -f "node_modules/@growi/slackbot-proxy"
 
 
     - name: Build
     - name: Build
       run: |
       run: |
@@ -61,11 +62,11 @@ jobs:
       run: |
       run: |
         tar -cf production.tar \
         tar -cf production.tar \
           package.json \
           package.json \
+          packages/app/.next \
           packages/app/config \
           packages/app/config \
           packages/app/public \
           packages/app/public \
           packages/app/resource \
           packages/app/resource \
           packages/app/tmp \
           packages/app/tmp \
-          packages/app/migrate-mongo-config.js \
           packages/app/.env.production* \
           packages/app/.env.production* \
           packages/*/package.json \
           packages/*/package.json \
           packages/*/dist
           packages/*/dist
@@ -81,7 +82,9 @@ jobs:
       uses: actions/upload-artifact@v3
       uses: actions/upload-artifact@v3
       with:
       with:
         name: Bundle Analyzing Report
         name: Bundle Analyzing Report
-        path: packages/app/report/bundle-analyzer.html
+        path: |
+          packages/app/.next/analyze/client.html
+          packages/app/.next/analyze/server.html
 
 
     - name: Slack Notification
     - name: Slack Notification
       uses: weseek/ghaction-slack-notification@master
       uses: weseek/ghaction-slack-notification@master
@@ -143,6 +146,7 @@ jobs:
     - name: Remove unnecessary packages
     - name: Remove unnecessary packages
       run: |
       run: |
         rm -rf packages/slackbot-proxy
         rm -rf packages/slackbot-proxy
+        rm -f "node_modules/@growi/slackbot-proxy"
 
 
     - name: lerna bootstrap --production
     - name: lerna bootstrap --production
       run: |
       run: |

+ 2 - 2
package.json

@@ -82,9 +82,9 @@
     "stylelint": "^14.2.0",
     "stylelint": "^14.2.0",
     "stylelint-config-recess-order": "^3.0.0",
     "stylelint-config-recess-order": "^3.0.0",
     "ts-jest": "^27.0.4",
     "ts-jest": "^27.0.4",
-    "ts-node": "^9.1.1",
+    "ts-node": "^10.9.1",
     "tsconfig-paths": "^3.9.0",
     "tsconfig-paths": "^3.9.0",
-    "typescript": "^4.7.3",
+    "typescript": "~4.7",
     "yargs": "^17.3.1"
     "yargs": "^17.3.1"
   },
   },
   "engines": {
   "engines": {

+ 4 - 0
packages/app/.eslintignore

@@ -1,7 +1,11 @@
+/_obsolete/**
 /dist/**
 /dist/**
+/transpiled/**
 /public/**
 /public/**
+/config/next-i18next.config.js
 /src/client/legacy/thirdparty-js/**
 /src/client/legacy/thirdparty-js/**
 /src/client/util/reveal/plugins/markdown.js
 /src/client/util/reveal/plugins/markdown.js
 /src/linter-checker/**
 /src/linter-checker/**
+/src/utils/next.config.utils.js
 /tmp/**
 /tmp/**
 /next-env.d.ts
 /next-env.d.ts

+ 11 - 1
packages/app/.eslintrc.js

@@ -38,9 +38,19 @@ module.exports = {
     '@typescript-eslint/no-var-requires': 'off',
     '@typescript-eslint/no-var-requires': 'off',
 
 
     // set 'warn' temporarily -- 2021.08.02 Yuki Takei
     // set 'warn' temporarily -- 2021.08.02 Yuki Takei
-    '@typescript-eslint/explicit-module-boundary-types': ['warn'],
     '@typescript-eslint/no-use-before-define': ['warn'],
     '@typescript-eslint/no-use-before-define': ['warn'],
     '@typescript-eslint/no-this-alias': ['warn'],
     '@typescript-eslint/no-this-alias': ['warn'],
     'jest/no-done-callback': ['warn'],
     'jest/no-done-callback': ['warn'],
   },
   },
+  overrides: [
+    {
+      // enable the rule specifically for TypeScript files
+      files: ['*.ts', '*.tsx'],
+      rules: {
+        // '@typescript-eslint/explicit-module-boundary-types': ['error'],
+        // set 'warn' temporarily -- 2022.07.25 Yuki Takei
+        '@typescript-eslint/explicit-module-boundary-types': ['warn'],
+      },
+    },
+  ],
 };
 };

+ 4 - 1
packages/app/.gitignore

@@ -11,12 +11,15 @@ test/cypress/videos
 /build/
 /build/
 /dist/
 /dist/
 /transpiled/
 /transpiled/
-/report/
 /public/static/js
 /public/static/js
 /public/static/styles
 /public/static/styles
 /public/uploads
 /public/uploads
 /tmp/
 /tmp/
 
 
+# transpiled configuration files for production build
+/config/next-i18next.config.js
+/src/utils/next.config.utils.js
+
 # dist (for GROWI v4.x and below)
 # dist (for GROWI v4.x and below)
 /public/*.chunk.js
 /public/*.chunk.js
 /public/*.chunk.js.LICENSE.txt
 /public/*.chunk.js.LICENSE.txt

+ 0 - 0
packages/app/config/webpack.common.js → packages/app/_obsolete/config/webpack.common.js


+ 0 - 0
packages/app/config/webpack.dev.dll.js → packages/app/_obsolete/config/webpack.dev.dll.js


+ 0 - 0
packages/app/config/webpack.dev.js → packages/app/_obsolete/config/webpack.dev.js


+ 0 - 0
packages/app/config/webpack.prod.js → packages/app/_obsolete/config/webpack.prod.js


+ 0 - 0
packages/app/src/client/admin.jsx → packages/app/_obsolete/src/client/admin.jsx


+ 1 - 1
packages/app/src/client/app.jsx → packages/app/_obsolete/src/client/app.jsx

@@ -35,7 +35,7 @@ import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLa
 import PageContentFooter from '../components/PageContentFooter';
 import PageContentFooter from '../components/PageContentFooter';
 import BookmarkList from '../components/PageList/BookmarkList';
 import BookmarkList from '../components/PageList/BookmarkList';
 import PageStatusAlert from '../components/PageStatusAlert';
 import PageStatusAlert from '../components/PageStatusAlert';
-import PageTimeline from '../components/PageTimeline';
+import { PageTimeline } from '../components/PageTimeline';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import { SearchPage } from '../components/SearchPage';
 import { SearchPage } from '../components/SearchPage';
 import Sidebar from '../components/Sidebar';
 import Sidebar from '../components/Sidebar';

+ 0 - 0
packages/app/src/client/base.jsx → packages/app/_obsolete/src/client/base.jsx


+ 0 - 0
packages/app/src/client/installer.jsx → packages/app/_obsolete/src/client/installer.jsx


+ 0 - 0
packages/app/src/client/nologin.jsx → packages/app/_obsolete/src/client/nologin.jsx


+ 6 - 1
packages/app/migrate-mongo-config.js → packages/app/config/migrate-mongo-config.js

@@ -4,16 +4,21 @@
  *
  *
  * @author Yuki Takei <yuki@weseek.co.jp>
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
  */
+const isProduction = process.env.NODE_ENV === 'production';
 
 
 const { URL } = require('url');
 const { URL } = require('url');
 
 
+const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = isProduction
+  // eslint-disable-next-line import/extensions, import/no-unresolved
+  ? require('../dist/server/util/mongoose-utils')
+  : require('../src/server/util/mongoose-utils');
+
 // get migrationsDir from env var
 // get migrationsDir from env var
 const migrationsDir = process.env.MIGRATIONS_DIR;
 const migrationsDir = process.env.MIGRATIONS_DIR;
 if (migrationsDir == null) {
 if (migrationsDir == null) {
   throw new Error('An env var MIGRATIONS_DIR must be set.');
   throw new Error('An env var MIGRATIONS_DIR must be set.');
 }
 }
 
 
-const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('@growi/core');
 
 
 initMongooseGlobalSettings();
 initMongooseGlobalSettings();
 
 

+ 0 - 0
packages/app/src/next-i18next.config.ts → packages/app/config/next-i18next.config.ts


+ 1 - 1
packages/app/docker/Dockerfile

@@ -111,11 +111,11 @@ RUN yarn lerna run build
 # make artifacts
 # make artifacts
 RUN tar -cf packages.tar \
 RUN tar -cf packages.tar \
   package.json \
   package.json \
+  packages/app/.next \
   packages/app/config \
   packages/app/config \
   packages/app/public \
   packages/app/public \
   packages/app/resource \
   packages/app/resource \
   packages/app/tmp \
   packages/app/tmp \
-  packages/app/migrate-mongo-config.js \
   packages/app/.env.production* \
   packages/app/.env.production* \
   packages/*/package.json \
   packages/*/package.json \
   packages/*/dist
   packages/*/dist

+ 92 - 70
packages/app/next.config.js

@@ -1,23 +1,33 @@
-import eazyLogger from 'eazy-logger';
-import { I18NextHMRPlugin } from 'i18next-hmr/plugin';
-import { withSuperjson } from 'next-superjson';
-import { WebpackManifestPlugin } from 'webpack-manifest-plugin';
+/**
+ * == Notes for production build==
+ * The modules required from this file must be transpiled before running `next build`.
+ *
+ * See: https://github.com/vercel/next.js/discussions/35969#discussioncomment-2522954
+ */
 
 
-import { i18n, localePath } from './src/next-i18next.config';
-import { listScopedPackages, listPrefixedPackages } from './src/utils/next.config.utils';
+const { withSuperjson } = require('next-superjson');
+const { PHASE_PRODUCTION_BUILD, PHASE_PRODUCTION_SERVER } = require('next/constants');
 
 
 
 
-// setup logger
-const logger = eazyLogger.Logger({
-  prefix: '[{green:next.config.js}] ',
-  useLevelPrefixes: false,
-});
+// define additional entries
+const additionalWebpackEntries = {
+  boot: './src/client/boot',
+};
 
 
 
 
-const setupWithTM = () => {
+const setupTranspileModules = () => {
+  const eazyLogger = require('eazy-logger');
+  const { listScopedPackages, listPrefixedPackages } = require('./src/utils/next.config.utils');
+
+  // setup logger
+  const logger = eazyLogger.Logger({
+    prefix: '[{green:next.config.js}] ',
+    useLevelPrefixes: false,
+  });
+
   // define transpiled packages for '@growi/*'
   // define transpiled packages for '@growi/*'
   const packages = [
   const packages = [
-    ...listScopedPackages(['@growi'], { ignorePackageNames: '@growi/app' }),
+    ...listScopedPackages(['@growi'], { ignorePackageNames: ['@growi/app'] }),
     // listing ESM packages until experimental.esmExternals works correctly to avoid ERR_REQUIRE_ESM
     // listing ESM packages until experimental.esmExternals works correctly to avoid ERR_REQUIRE_ESM
     'react-markdown',
     'react-markdown',
     'unified',
     'unified',
@@ -39,63 +49,75 @@ const setupWithTM = () => {
 
 
   return require('next-transpile-modules')(packages);
   return require('next-transpile-modules')(packages);
 };
 };
-const withTM = setupWithTM();
-
-
-// define additional entries
-const additionalWebpackEntries = {
-  boot: './src/client/boot',
-};
-
 
 
-/** @type {import('next').NextConfig} */
-const nextConfig = {
-  // == DOES NOT WORK
-  // see: https://github.com/vercel/next.js/discussions/27876
-  // experimental: { esmExternals: true }, // Prefer loading of ES Modules over CommonJS
-
-  reactStrictMode: true,
-  typescript: {
-    tsconfigPath: 'tsconfig.build.client.json',
-  },
-  pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'],
-
-  i18n,
-
-  /** @param config {import('next').NextConfig} */
-  webpack(config, options) {
-    // Avoid "Module not found: Can't resolve 'fs'"
-    // See: https://stackoverflow.com/a/68511591
-    if (!options.isServer) {
-      config.resolve.fallback.fs = false;
-    }
-
-    // See: https://webpack.js.org/configuration/externals/
-    // This provides a way of excluding dependencies from the output bundles
-    config.externals.push('dtrace-provider');
-
-    // configure additional entries
-    const orgEntry = config.entry;
-    config.entry = () => {
-      return orgEntry().then((entry) => {
-        return { ...entry, ...additionalWebpackEntries };
-      });
-    };
-
-    config.plugins.push(
-      new WebpackManifestPlugin({
-        fileName: 'custom-manifest.json',
-      }),
-    );
-
-    // setup i18next-hmr
-    if (!options.isServer && options.dev) {
-      config.plugins.push(new I18NextHMRPlugin({ localesDir: localePath }));
-    }
-
-    return config;
-  },
 
 
+module.exports = async(phase, { defaultConfig }) => {
+
+  const { i18n, localePath } = require('./config/next-i18next.config');
+
+  /** @type {import('next').NextConfig} */
+  const nextConfig = {
+    // == DOES NOT WORK
+    // see: https://github.com/vercel/next.js/discussions/27876
+    // experimental: { esmExternals: true }, // Prefer loading of ES Modules over CommonJS
+
+    reactStrictMode: true,
+    swcMinify: true,
+    typescript: {
+      tsconfigPath: 'tsconfig.build.client.json',
+    },
+    pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'],
+
+    i18n,
+
+    /** @param config {import('next').NextConfig} */
+    webpack(config, options) {
+      // Avoid "Module not found: Can't resolve 'fs'"
+      // See: https://stackoverflow.com/a/68511591
+      if (!options.isServer) {
+        config.resolve.fallback.fs = false;
+      }
+
+      // See: https://webpack.js.org/configuration/externals/
+      // This provides a way of excluding dependencies from the output bundles
+      config.externals.push('dtrace-provider');
+      config.externals.push('mongoose');
+
+      // configure additional entries
+      const orgEntry = config.entry;
+      config.entry = () => {
+        return orgEntry().then((entry) => {
+          return { ...entry, ...additionalWebpackEntries };
+        });
+      };
+
+      const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
+      config.plugins.push(
+        new WebpackManifestPlugin({
+          fileName: 'custom-manifest.json',
+        }),
+      );
+
+      // setup i18next-hmr
+      if (!options.isServer && options.dev) {
+        const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+        config.plugins.push(new I18NextHMRPlugin({ localesDir: localePath }));
+      }
+
+      return config;
+    },
+
+  };
+
+  // production server
+  if (phase === PHASE_PRODUCTION_SERVER) {
+    return withSuperjson()(nextConfig);
+  }
+
+  const withTM = setupTranspileModules();
+  const withBundleAnalyzer = require('@next/bundle-analyzer')({
+    enabled: phase === PHASE_PRODUCTION_BUILD || process.env.ANALYZE === 'true',
+  });
+
+  return withBundleAnalyzer(withTM(withSuperjson()(nextConfig)));
 };
 };
-
-module.exports = withSuperjson()(withTM(nextConfig));

+ 16 - 14
packages/app/package.json

@@ -4,29 +4,28 @@
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "//// for production": "",
     "//// for production": "",
-    "build": "yarn next build",
+    "build": "run-p build:*",
     "start": "yarn next start",
     "start": "yarn next start",
-    "//// for production (obsolete)": "",
-    "start:obsolete": "yarn build && yarn server",
-    "build:obsolete": "run-p build:*",
-    "build:client": "yarn cross-env NODE_ENV=production webpack --config config/webpack.prod.js",
+    "build:client": "yarn next build",
+    "prebuild:client": "tsc -p tsconfig.build.next.config.json",
     "build:server": "yarn cross-env NODE_ENV=production tsc -p tsconfig.build.server.json && tsc-alias -p tsconfig.build.server.json",
     "build:server": "yarn cross-env NODE_ENV=production tsc -p tsconfig.build.server.json && tsc-alias -p tsconfig.build.server.json",
+    "postbuild:server": "npx -y shx mv transpiled/src dist && npx -y shx cp -r transpiled/config/* config && npx -y shx cp -r src/server/views dist/server/ && npx -y shx rm -rf transpiled",
     "clean": "npx -y shx rm -rf dist transpiled",
     "clean": "npx -y shx rm -rf dist transpiled",
     "prebuild": "yarn cross-env NODE_ENV=production run-p clean resources:*",
     "prebuild": "yarn cross-env NODE_ENV=production run-p clean resources:*",
-    "postbuild": "npx -y shx mv transpiled/src dist && npx -y shx cp -r transpiled/config/* config && npx -y shx cp -r src/server/views dist/server/ && npx -y shx rm -rf transpiled",
     "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config dist/server/app.js",
     "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config dist/server/app.js",
     "server:ci": "yarn server --ci",
     "server:ci": "yarn server --ci",
     "preserver": "yarn cross-env NODE_ENV=production yarn migrate",
     "preserver": "yarn cross-env NODE_ENV=production yarn migrate",
-    "migrate": "node -r dotenv-flow/config node_modules/.bin/migrate-mongo up",
+    "migrate": "node -r dotenv-flow/config node_modules/.bin/migrate-mongo up -f config/migrate-mongo-config.js",
     "//// for development": "",
     "//// for development": "",
     "dev": "yarn cross-env NODE_ENV=development ts-node-dev -r tsconfig-paths/register -r dotenv-flow/config --inspect --transpile-only src/server/app.ts",
     "dev": "yarn cross-env NODE_ENV=development ts-node-dev -r tsconfig-paths/register -r dotenv-flow/config --inspect --transpile-only src/server/app.ts",
     "predev": "yarn cross-env NODE_ENV=development run-p resources:* dev:migrate:up",
     "predev": "yarn cross-env NODE_ENV=development run-p resources:* dev:migrate:up",
+    "dev:analyze": "yarn cross-env ANALYZE=true yarn dev",
     "dev:migrate-mongo": "yarn cross-env NODE_ENV=development yarn ts-node node_modules/.bin/migrate-mongo",
     "dev:migrate-mongo": "yarn cross-env NODE_ENV=development yarn ts-node node_modules/.bin/migrate-mongo",
-    "dev:migrate": "yarn dev:migrate:up",
-    "dev:migrate:create": "yarn dev:migrate-mongo create",
-    "dev:migrate:status": "yarn dev:migrate-mongo status",
-    "dev:migrate:up": "yarn dev:migrate-mongo up",
-    "dev:migrate:down": "yarn dev:migrate-mongo down",
+    "dev:migrate": "yarn dev:migrate:up -f config/migrate-mongo-config.js",
+    "dev:migrate:create": "yarn dev:migrate-mongo create -f config/migrate-mongo-config.js",
+    "dev:migrate:status": "yarn dev:migrate-mongo status -f config/migrate-mongo-config.js",
+    "dev:migrate:up": "yarn dev:migrate-mongo up -f config/migrate-mongo-config.js",
+    "dev:migrate:down": "yarn dev:migrate-mongo down -f config/migrate-mongo-config.js",
     "cy:run": "cypress run --browser chrome",
     "cy:run": "cypress run --browser chrome",
     "//// for CI": "",
     "//// for CI": "",
     "dev:ci": "yarn dev --ci",
     "dev:ci": "yarn dev --ci",
@@ -64,6 +63,7 @@
     "@godaddy/terminus": "^4.9.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@google-cloud/storage": "^5.8.5",
     "@growi/codemirror-textlint": "^5.1.1-RC.0",
     "@growi/codemirror-textlint": "^5.1.1-RC.0",
+    "@growi/core": "^5.1.1-RC.0",
     "@growi/plugin-attachment-refs": "^5.1.1-RC.0",
     "@growi/plugin-attachment-refs": "^5.1.1-RC.0",
     "@growi/plugin-lsx": "^5.1.1-RC.0",
     "@growi/plugin-lsx": "^5.1.1-RC.0",
     "@growi/plugin-pukiwiki-like-linker": "^5.1.1-RC.0",
     "@growi/plugin-pukiwiki-like-linker": "^5.1.1-RC.0",
@@ -82,6 +82,7 @@
     "axios-retry": "^3.2.4",
     "axios-retry": "^3.2.4",
     "body-parser": "^1.18.2",
     "body-parser": "^1.18.2",
     "browser-bunyan": "^1.6.3",
     "browser-bunyan": "^1.6.3",
+    "bson-objectid": "^2.0.3",
     "bunyan": "^1.8.15",
     "bunyan": "^1.8.15",
     "check-node-version": "^4.1.0",
     "check-node-version": "^4.1.0",
     "compression": "^1.7.4",
     "compression": "^1.7.4",
@@ -126,6 +127,7 @@
     "multer-autoreap": "^1.0.3",
     "multer-autoreap": "^1.0.3",
     "next": "^12.1.6",
     "next": "^12.1.6",
     "next-i18next": "^11.0.0",
     "next-i18next": "^11.0.0",
+    "next-superjson": "^0.0.4",
     "next-themes": "^0.2.0",
     "next-themes": "^0.2.0",
     "nocache": "^3.0.1",
     "nocache": "^3.0.1",
     "nodemailer": "^6.6.2",
     "nodemailer": "^6.6.2",
@@ -183,6 +185,8 @@
     "@alienfast/i18next-loader": "^1.1.4",
     "@alienfast/i18next-loader": "^1.1.4",
     "@growi/ui": "^5.1.1-RC.0",
     "@growi/ui": "^5.1.1-RC.0",
     "@handsontable/react": "=2.1.0",
     "@handsontable/react": "=2.1.0",
+    "@icon/themify-icons": "1.0.1-alpha.3",
+    "@next/bundle-analyzer": "^12.2.3",
     "@types/compression": "^1.7.0",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",
     "@types/express": "^4.17.11",
     "@types/jquery": "^3.5.8",
     "@types/jquery": "^3.5.8",
@@ -214,7 +218,6 @@
     "markdown-table": "^1.1.1",
     "markdown-table": "^1.1.1",
     "material-icons": "^1.11.3",
     "material-icons": "^1.11.3",
     "morgan": "^1.10.0",
     "morgan": "^1.10.0",
-    "next-superjson": "^0.0.4",
     "next-transpile-modules": "^9.0.0",
     "next-transpile-modules": "^9.0.0",
     "normalize-path": "^3.0.0",
     "normalize-path": "^3.0.0",
     "penpal": "^4.0.0",
     "penpal": "^4.0.0",
@@ -239,7 +242,6 @@
     "sticky-events": "^3.4.11",
     "sticky-events": "^3.4.11",
     "swagger2openapi": "^5.3.1",
     "swagger2openapi": "^5.3.1",
     "swr": "^1.3.0",
     "swr": "^1.3.0",
-    "@icon/themify-icons": "1.0.1-alpha.3",
     "throttle-debounce": "^3.0.1",
     "throttle-debounce": "^3.0.1",
     "toastr": "^2.1.2",
     "toastr": "^2.1.2",
     "ts-node-dev": "^2.0.0",
     "ts-node-dev": "^2.0.0",

+ 3 - 0
packages/app/public/static/locales/en_US/admin.json

@@ -529,6 +529,7 @@
     }
     }
   },
   },
   "audit_log_management": {
   "audit_log_management": {
+    "user": "User",
     "username": "Username",
     "username": "Username",
     "date": "Date",
     "date": "Date",
     "action": "Action",
     "action": "Action",
@@ -536,6 +537,8 @@
     "url": "URL",
     "url": "URL",
     "settings": "Settings",
     "settings": "Settings",
     "return": "Return",
     "return": "Return",
+    "clear": "Clear search criteria",
+    "reload": "Reload",
     "activity_expiration_date": "Audit Log expiration date",
     "activity_expiration_date": "Audit Log expiration date",
     "activity_expiration_date_explain": "Created Audit Log are automatically deleted after the number of seconds set in the environment variable from the creation time",
     "activity_expiration_date_explain": "Created Audit Log are automatically deleted after the number of seconds set in the environment variable from the creation time",
     "fixed_by_env_var": "This is fixed by the env var <code>{{key}}={{value}}</code>.",
     "fixed_by_env_var": "This is fixed by the env var <code>{{key}}={{value}}</code>.",

+ 1 - 0
packages/app/public/static/locales/en_US/translation.json

@@ -1101,6 +1101,7 @@
       "no_grant_available": "The list of selectable permissions could not be found. Please modify the permissions on the parent page first and try again.",
       "no_grant_available": "The list of selectable permissions could not be found. Please modify the permissions on the parent page first and try again.",
       "need_to_fix_grant": "The permissions associated with this page must be modified in order to use the functionality correctly. <br> Please select from the options below to make the change.",
       "need_to_fix_grant": "The permissions associated with this page must be modified in order to use the functionality correctly. <br> Please select from the options below to make the change.",
       "grant_label": {
       "grant_label": {
+        "public": "Public",
         "isForbidden": "Authority not allowed to view",
         "isForbidden": "Authority not allowed to view",
         "currentPageGrantLabel": "Authorization for this page: ",
         "currentPageGrantLabel": "Authorization for this page: ",
         "parentPageGrantLabel": "Authority of parent page: ",
         "parentPageGrantLabel": "Authority of parent page: ",

+ 3 - 0
packages/app/public/static/locales/ja_JP/admin.json

@@ -528,6 +528,7 @@
     }
     }
   },
   },
   "audit_log_management": {
   "audit_log_management": {
+    "user": "ユーザー",
     "username": "ユーザー名",
     "username": "ユーザー名",
     "date": "日付",
     "date": "日付",
     "action": "アクション",
     "action": "アクション",
@@ -535,6 +536,8 @@
     "url": "URL",
     "url": "URL",
     "settings": "設定",
     "settings": "設定",
     "return": "戻る",
     "return": "戻る",
+    "clear": "検索条件のクリア",
+    "reload": "再読み込み",
     "activity_expiration_date": "監査ログの有効期限",
     "activity_expiration_date": "監査ログの有効期限",
     "activity_expiration_date_explain": "作成された監査ログは、作成時間から環境変数に設定した秒数後に自動的に削除されます",
     "activity_expiration_date_explain": "作成された監査ログは、作成時間から環境変数に設定した秒数後に自動的に削除されます",
     "fixed_by_env_var": "環境変数により固定されています <code>{{key}}={{value}}</code>.",
     "fixed_by_env_var": "環境変数により固定されています <code>{{key}}={{value}}</code>.",

+ 1 - 0
packages/app/public/static/locales/ja_JP/translation.json

@@ -1094,6 +1094,7 @@
       "no_grant_available": "選択可能な権限のリストが見つかりませんでした。まず親ページの権限を修正したのちに再試行してください。",
       "no_grant_available": "選択可能な権限のリストが見つかりませんでした。まず親ページの権限を修正したのちに再試行してください。",
       "need_to_fix_grant": "正しく機能を使用するためにはこのページに紐づく権限を修正する必要があります。 <br> 下記の選択肢から選んで変更してください。",
       "need_to_fix_grant": "正しく機能を使用するためにはこのページに紐づく権限を修正する必要があります。 <br> 下記の選択肢から選んで変更してください。",
       "grant_label": {
       "grant_label": {
+        "public": "公開",
         "isForbidden": "権限の閲覧が許可されていません",
         "isForbidden": "権限の閲覧が許可されていません",
         "currentPageGrantLabel": "このページの権限: ",
         "currentPageGrantLabel": "このページの権限: ",
         "parentPageGrantLabel": "親のページの権限: ",
         "parentPageGrantLabel": "親のページの権限: ",

+ 3 - 0
packages/app/public/static/locales/zh_CN/admin.json

@@ -538,6 +538,7 @@
     }
     }
   },
   },
   "audit_log_management": {
   "audit_log_management": {
+    "user": "用户",
     "username": "帐号",
     "username": "帐号",
     "date": "日期",
     "date": "日期",
     "action": "行动",
     "action": "行动",
@@ -545,6 +546,8 @@
     "url": "URL",
     "url": "URL",
     "settings": "设置",
     "settings": "设置",
     "return": "返回",
     "return": "返回",
+    "clear": "清除搜索标准",
+    "reload": "重新加载",
     "activity_expiration_date": "审计日志的到期日",
     "activity_expiration_date": "审计日志的到期日",
     "activity_expiration_date_explain": "创建的审计日志会在环境变量中设置的从创建时间算起的秒数后自动删除",
     "activity_expiration_date_explain": "创建的审计日志会在环境变量中设置的从创建时间算起的秒数后自动删除",
     "fixed_by_env_var": "这是由env var 修复的 <code>{{key}}={{value}}</code>.",
     "fixed_by_env_var": "这是由env var 修复的 <code>{{key}}={{value}}</code>.",

+ 1 - 0
packages/app/public/static/locales/zh_CN/translation.json

@@ -1104,6 +1104,7 @@
       "no_grant_available": "无法找到可选择的权限列表。 请先修改父页的权限,然后再试一次。",
       "no_grant_available": "无法找到可选择的权限列表。 请先修改父页的权限,然后再试一次。",
       "need_to_fix_grant": "为了正确使用该功能,需要修改与该页面相关的权限。 <br> 请从以下选项中选择进行更改。",
       "need_to_fix_grant": "为了正确使用该功能,需要修改与该页面相关的权限。 <br> 请从以下选项中选择进行更改。",
       "grant_label": {
       "grant_label": {
+        "public": "向公众提供",
         "isForbidden": "无权查看的机构",
         "isForbidden": "无权查看的机构",
         "currentPageGrantLabel": "本页的权限: ",
         "currentPageGrantLabel": "本页的权限: ",
         "parentPageGrantLabel": "父页的权限: ",
         "parentPageGrantLabel": "父页的权限: ",

+ 3 - 0
packages/app/src/client/interfaces/clearable.ts

@@ -0,0 +1,3 @@
+export interface IClearable {
+  clear: () => void,
+}

+ 1 - 1
packages/app/src/client/util/apiv3-client.ts

@@ -13,7 +13,7 @@ const apiv3Root = '/_api/v3';
 const logger = loggerFactory('growi:apiv3');
 const logger = loggerFactory('growi:apiv3');
 
 
 
 
-const apiv3ErrorHandler = (_err) => {
+const apiv3ErrorHandler = (_err: any): any[] => {
   // extract api errors from general 400 err
   // extract api errors from general 400 err
   const err = _err.response ? _err.response.data.errors : _err;
   const err = _err.response ? _err.response.data.errors : _err;
   const errs = toArrayIfNot(err);
   const errs = toArrayIfNot(err);

+ 1 - 1
packages/app/src/client/util/i18n.js

@@ -17,7 +17,7 @@ Object.values(locales).forEach((locale) => {
 });
 });
 
 
 /*
 /*
-* Note: This file will be deleted. use "~/next-i18next.config" instead
+* Note: This file will be deleted. use "^/config/next-i18next.config" instead
 */
 */
 // extract metadata list from 'public/static/locales/${locale}/meta.json'
 // extract metadata list from 'public/static/locales/${locale}/meta.json'
 export const localeMetadatas = Object.values(locales).map(locale => locale.meta);
 export const localeMetadatas = Object.values(locales).map(locale => locale.meta);

+ 2 - 1
packages/app/src/components/Admin/App/AppSetting.jsx

@@ -3,9 +3,10 @@ import React, { useCallback } from 'react';
 import { useTranslation, i18n } from 'next-i18next';
 import { useTranslation, i18n } from 'next-i18next';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
+import { i18n as i18nConfig } from '^/config/next-i18next.config';
+
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { i18n as i18nConfig } from '~/next-i18next.config';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

+ 11 - 2
packages/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -1,5 +1,7 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
 
 
+import { pagePathUtils } from '@growi/core';
+import { UserPicture } from '@growi/ui';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
@@ -21,7 +23,7 @@ export const ActivityTable : FC<Props> = (props: Props) => {
       <table className="table table-default table-bordered table-user-list">
       <table className="table table-default table-bordered table-user-list">
         <thead>
         <thead>
           <tr>
           <tr>
-            <th scope="col">{t('admin:audit_log_management.username')}</th>
+            <th scope="col">{t('admin:audit_log_management.user')}</th>
             <th scope="col">{t('admin:audit_log_management.date')}</th>
             <th scope="col">{t('admin:audit_log_management.date')}</th>
             <th scope="col">{t('admin:audit_log_management.action')}</th>
             <th scope="col">{t('admin:audit_log_management.action')}</th>
             <th scope="col">{t('admin:audit_log_management.ip')}</th>
             <th scope="col">{t('admin:audit_log_management.ip')}</th>
@@ -32,7 +34,14 @@ export const ActivityTable : FC<Props> = (props: Props) => {
           {props.activityList.map((activity) => {
           {props.activityList.map((activity) => {
             return (
             return (
               <tr data-testid="activity-table" key={activity._id}>
               <tr data-testid="activity-table" key={activity._id}>
-                <td>{activity.snapshot?.username}</td>
+                <td>
+                  { activity.user != null && (
+                    <>
+                      <UserPicture user={activity.user} className="picture rounded-circle" />
+                      <a className="ml-2" href={pagePathUtils.userPageRoot(activity.user)}>{activity.snapshot?.username}</a>
+                    </>
+                  )}
+                </td>
                 <td>{formatDate(activity.createdAt)}</td>
                 <td>{formatDate(activity.createdAt)}</td>
                 <td>{t(`admin:audit_log_action.${activity.action}`)}</td>
                 <td>{t(`admin:audit_log_action.${activity.action}`)}</td>
                 <td>{activity.ip}</td>
                 <td>{activity.ip}</td>

+ 18 - 3
packages/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx

@@ -1,10 +1,11 @@
 import React, {
 import React, {
-  FC, Fragment, useState, useCallback,
+  Fragment, useState, useCallback, useRef, ForwardRefRenderFunction, forwardRef, useImperativeHandle,
 } from 'react';
 } from 'react';
 
 
 import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
+import { IClearable } from '~/client/interfaces/clearable';
 import { useSWRxUsernames } from '~/stores/user';
 import { useSWRxUsernames } from '~/stores/user';
 
 
 
 
@@ -25,10 +26,12 @@ type Props = {
   onChange: (text: string[]) => void
   onChange: (text: string[]) => void
 }
 }
 
 
-export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
+const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Props> = ((props: Props, ref) => {
   const { onChange } = props;
   const { onChange } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const typeaheadRef = useRef<IClearable>(null);
+
   /*
   /*
    * State
    * State
    */
    */
@@ -96,6 +99,15 @@ export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
     );
     );
   }, []);
   }, []);
 
 
+  useImperativeHandle(ref, () => ({
+    clear() {
+      const instance = typeaheadRef?.current;
+      if (instance != null) {
+        instance.clear();
+      }
+    },
+  }));
+
   return (
   return (
     <div className="input-group mr-2">
     <div className="input-group mr-2">
       <div className="input-group-prepend">
       <div className="input-group-prepend">
@@ -104,6 +116,7 @@ export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
         </span>
         </span>
       </div>
       </div>
       <AsyncTypeahead
       <AsyncTypeahead
+        ref={typeaheadRef}
         id="search-username-typeahead-asynctypeahead"
         id="search-username-typeahead-asynctypeahead"
         multiple
         multiple
         delay={400}
         delay={400}
@@ -119,4 +132,6 @@ export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
       />
       />
     </div>
     </div>
   );
   );
-};
+});
+
+export const SearchUsernameTypeahead = forwardRef(SearchUsernameTypeaheadSubstance);

+ 32 - 8
packages/app/src/components/Admin/AuditLogManagement.tsx

@@ -1,8 +1,11 @@
-import React, { FC, useState, useCallback } from 'react';
+import React, {
+  FC, useState, useCallback, useRef,
+} from 'react';
 
 
 import { format } from 'date-fns';
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
+import { IClearable } from '~/client/interfaces/clearable';
 import { toastError } from '~/client/util/apiNotification';
 import { toastError } from '~/client/util/apiNotification';
 import { SupportedActionType } from '~/interfaces/activity';
 import { SupportedActionType } from '~/interfaces/activity';
 import { useSWRxActivity } from '~/stores/activity';
 import { useSWRxActivity } from '~/stores/activity';
@@ -17,7 +20,6 @@ import { DateRangePicker } from './AuditLog/DateRangePicker';
 import { SearchUsernameTypeahead } from './AuditLog/SearchUsernameTypeahead';
 import { SearchUsernameTypeahead } from './AuditLog/SearchUsernameTypeahead';
 import { SelectActionDropdown } from './AuditLog/SelectActionDropdown';
 import { SelectActionDropdown } from './AuditLog/SelectActionDropdown';
 
 
-
 const formatDate = (date: Date | null) => {
 const formatDate = (date: Date | null) => {
   if (date == null) {
   if (date == null) {
     return '';
     return '';
@@ -30,8 +32,9 @@ const PAGING_LIMIT = 10;
 export const AuditLogManagement: FC = () => {
 export const AuditLogManagement: FC = () => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const typeaheadRef = useRef<IClearable>(null);
+
   const { data: auditLogAvailableActionsData } = useAuditLogAvailableActions();
   const { data: auditLogAvailableActionsData } = useAuditLogAvailableActions();
-  const auditLogAvailableActions = auditLogAvailableActionsData != null ? auditLogAvailableActionsData : [];
 
 
   /*
   /*
    * State
    * State
@@ -43,7 +46,7 @@ export const AuditLogManagement: FC = () => {
   const [endDate, setEndDate] = useState<Date | null>(null);
   const [endDate, setEndDate] = useState<Date | null>(null);
   const [selectedUsernames, setSelectedUsernames] = useState<string[]>([]);
   const [selectedUsernames, setSelectedUsernames] = useState<string[]>([]);
   const [actionMap, setActionMap] = useState(
   const [actionMap, setActionMap] = useState(
-    new Map<SupportedActionType, boolean>(auditLogAvailableActions.map(action => [action, true])),
+    new Map<SupportedActionType, boolean>(auditLogAvailableActionsData != null ? auditLogAvailableActionsData.map(action => [action, true]) : []),
   );
   );
 
 
   /*
   /*
@@ -94,6 +97,18 @@ export const AuditLogManagement: FC = () => {
     setSelectedUsernames(usernames);
     setSelectedUsernames(usernames);
   }, []);
   }, []);
 
 
+  const clearButtonPushedHandler = useCallback(() => {
+    setActivePage(1);
+    setStartDate(null);
+    setEndDate(null);
+    setSelectedUsernames([]);
+    typeaheadRef.current?.clear();
+
+    if (auditLogAvailableActionsData != null) {
+      setActionMap(new Map<SupportedActionType, boolean>(auditLogAvailableActionsData.map(action => [action, true])));
+    }
+  }, [setActivePage, setStartDate, setEndDate, setSelectedUsernames, setActionMap, auditLogAvailableActionsData]);
+
   const reloadButtonPushedHandler = useCallback(() => {
   const reloadButtonPushedHandler = useCallback(() => {
     setActivePage(1);
     setActivePage(1);
     mutateActivity();
     mutateActivity();
@@ -128,6 +143,7 @@ export const AuditLogManagement: FC = () => {
         <>
         <>
           <div className="form-inline mb-3">
           <div className="form-inline mb-3">
             <SearchUsernameTypeahead
             <SearchUsernameTypeahead
+              ref={typeaheadRef}
               onChange={setUsernamesHandler}
               onChange={setUsernamesHandler}
             />
             />
 
 
@@ -139,14 +155,22 @@ export const AuditLogManagement: FC = () => {
 
 
             <SelectActionDropdown
             <SelectActionDropdown
               actionMap={actionMap}
               actionMap={actionMap}
-              availableActions={auditLogAvailableActions}
+              availableActions={auditLogAvailableActionsData || []}
               onChangeAction={actionCheckboxChangedHandler}
               onChangeAction={actionCheckboxChangedHandler}
               onChangeMultipleAction={multipleActionCheckboxChangedHandler}
               onChangeMultipleAction={multipleActionCheckboxChangedHandler}
             />
             />
 
 
-            <button type="button" className="btn ml-auto grw-btn-reload" onClick={reloadButtonPushedHandler}>
-              <i className="icon icon-reload" />
-            </button>
+            <div className="ml-auto">
+              <button type="button" className="btn btn-outline-secondary btn-sm mr-2" onClick={clearButtonPushedHandler}>
+                <span className="icon-refresh mr-1" />
+                {t('admin:audit_log_management.clear')}
+              </button>
+
+              <button type="button" className="btn btn-outline-secondary btn-sm" onClick={reloadButtonPushedHandler}>
+                <i className="icon icon-reload mr-1" />
+                {t('admin:audit_log_management.reload')}
+              </button>
+            </div>
           </div>
           </div>
 
 
           <p
           <p

+ 1 - 1
packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx

@@ -12,7 +12,7 @@ const CustomizeLayoutSetting = (): JSX.Element => {
   const { resolvedTheme } = useNextThemes();
   const { resolvedTheme } = useNextThemes();
 
 
   const [isContainerFluid, setIsContainerFluid] = useState(false);
   const [isContainerFluid, setIsContainerFluid] = useState(false);
-  const [retrieveError, setRetrieveError] = useState();
+  const [retrieveError, setRetrieveError] = useState<any>();
 
 
   const retrieveData = useCallback(async() => {
   const retrieveData = useCallback(async() => {
     try {
     try {

+ 1 - 1
packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx

@@ -19,7 +19,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
   const [uploadLogoSrc, setUploadLogoSrc] = useState<ArrayBuffer | string | null>(null);
   const [uploadLogoSrc, setUploadLogoSrc] = useState<ArrayBuffer | string | null>(null);
   const [isImageCropModalShow, setIsImageCropModalShow] = useState<boolean>(false);
   const [isImageCropModalShow, setIsImageCropModalShow] = useState<boolean>(false);
   const [isDefaultLogo, setIsDefaultLogo] = useState<boolean>(true);
   const [isDefaultLogo, setIsDefaultLogo] = useState<boolean>(true);
-  const [retrieveError, setRetrieveError] = useState<string | null>(null);
+  const [retrieveError, setRetrieveError] = useState<any>();
   const [customizedLogoSrc, setCustomizedLogoSrc] = useState< string | null >(null);
   const [customizedLogoSrc, setCustomizedLogoSrc] = useState< string | null >(null);
 
 
   const retrieveData = useCallback(async() => {
   const retrieveData = useCallback(async() => {

+ 6 - 4
packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx

@@ -44,13 +44,15 @@ const ElasticsearchManagement = () => {
       setAliasesData(info.aliases);
       setAliasesData(info.aliases);
       setIsNormalized(info.isNormalized);
       setIsNormalized(info.isNormalized);
     }
     }
-    catch (errors) {
+    catch (errors: unknown) {
       setIsConnected(false);
       setIsConnected(false);
 
 
       // evaluate whether configured or not
       // evaluate whether configured or not
-      for (const error of errors) {
-        if (error.code === 'search-service-unconfigured') {
-          setIsConfigured(false);
+      if (Array.isArray(errors)) {
+        for (const error of errors) {
+          if (error.code === 'search-service-unconfigured') {
+            setIsConfigured(false);
+          }
         }
         }
       }
       }
 
 

+ 3 - 1
packages/app/src/components/CustomNavigation/CustomNav.jsx

@@ -7,6 +7,8 @@ import {
   Nav, NavItem, NavLink,
   Nav, NavItem, NavLink,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
+import styles from './CustomNav.module.scss';
+
 
 
 function getBreakpointOneLevelLarger(breakpoint) {
 function getBreakpointOneLevelLarger(breakpoint) {
   switch (breakpoint) {
   switch (breakpoint) {
@@ -149,7 +151,7 @@ export const CustomNavTab = (props) => {
   }
   }
 
 
   return (
   return (
-    <div className="grw-custom-nav-tab">
+    <div className={`grw-custom-nav-tab ${styles['grw-custom-nav-tab']}`}>
       <div ref={navContainer} className="d-flex justify-content-between">
       <div ref={navContainer} className="d-flex justify-content-between">
         <Nav className="nav-title">
         <Nav className="nav-title">
           {Object.entries(navTabMapping).map(([key, value]) => {
           {Object.entries(navTabMapping).map(([key, value]) => {

+ 8 - 6
packages/app/src/styles/_navbar.scss → packages/app/src/components/CustomNavigation/CustomNav.module.scss

@@ -1,14 +1,16 @@
 .grw-custom-nav-tab,
 .grw-custom-nav-tab,
 .grw-custom-nav-dropdown {
 .grw-custom-nav-dropdown {
-  svg {
-    width: 17px;
-    height: 17px;
-    margin-right: 5px;
-    vertical-align: text-bottom;
+  :global {
+    svg {
+      width: 17px;
+      height: 17px;
+      margin-right: 5px;
+      vertical-align: text-bottom;
+    }
   }
   }
 }
 }
 
 
-.grw-custom-nav-tab {
+.grw-custom-nav-tab :global {
   .nav-title {
   .nav-title {
     flex-wrap: nowrap;
     flex-wrap: nowrap;
   }
   }

+ 12 - 18
packages/app/src/components/CustomNavigation/CustomNavAndContents.jsx → packages/app/src/components/CustomNavigation/CustomNavAndContents.tsx

@@ -1,14 +1,21 @@
-import React, { useState } from 'react';
-
-import PropTypes from 'prop-types';
+import React, { ReactNode, useState } from 'react';
 
 
 import CustomNav, { CustomNavTab, CustomNavDropdown } from './CustomNav';
 import CustomNav, { CustomNavTab, CustomNavDropdown } from './CustomNav';
 import CustomTabContent from './CustomTabContent';
 import CustomTabContent from './CustomTabContent';
 
 
+type CustomNavAndContentsProps = {
+  navTabMapping: any,
+  defaultTabIndex?: number,
+  navigationMode?: 'both' | 'tab' | 'dropdown',
+  tabContentClasses?: string[],
+  breakpointToHideInactiveTabsDown?: 'xs' | 'sm' | 'md' | 'lg' | 'xl',
+  navRightElement?: ReactNode
+}
+
 
 
-const CustomNavAndContents = (props) => {
+const CustomNavAndContents = (props: CustomNavAndContentsProps): JSX.Element => {
   const {
   const {
-    navTabMapping, defaultTabIndex, navigationMode, tabContentClasses, breakpointToHideInactiveTabsDown, navRightElement,
+    navTabMapping, defaultTabIndex, navigationMode = 'tab', tabContentClasses = ['p-4'], breakpointToHideInactiveTabsDown, navRightElement,
   } = props;
   } = props;
   const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
   const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
 
 
@@ -39,17 +46,4 @@ const CustomNavAndContents = (props) => {
   );
   );
 };
 };
 
 
-CustomNavAndContents.propTypes = {
-  navTabMapping: PropTypes.object.isRequired,
-  defaultTabIndex: PropTypes.number,
-  navigationMode: PropTypes.oneOf(['both', 'tab', 'dropdown']),
-  tabContentClasses: PropTypes.arrayOf(PropTypes.string),
-  breakpointToHideInactiveTabsDown: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
-  navRightElement: PropTypes.node,
-};
-CustomNavAndContents.defaultProps = {
-  navigationMode: 'tab',
-  tabContentClasses: ['p-4'],
-};
-
 export default CustomNavAndContents;
 export default CustomNavAndContents;

+ 1 - 1
packages/app/src/components/DescendantsPageListModal.tsx

@@ -15,7 +15,7 @@ import { DescendantsPageList } from './DescendantsPageList';
 import ExpandOrContractButton from './ExpandOrContractButton';
 import ExpandOrContractButton from './ExpandOrContractButton';
 import PageListIcon from './Icons/PageListIcon';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
-import PageTimeline from './PageTimeline';
+import { PageTimeline } from './PageTimeline';
 
 
 
 
 export const DescendantsPageListModal = (): JSX.Element => {
 export const DescendantsPageListModal = (): JSX.Element => {

+ 10 - 3
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -10,11 +10,12 @@ import { exportAsMarkdown } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
 import {
 import {
-  IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity,
+  IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity, IPageHasId,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
 import {
   useCurrentPageId,
   useCurrentPageId,
+  useCurrentPathname,
   useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId, useTemplateTagData,
   useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId, useTemplateTagData,
 } from '~/stores/context';
 } from '~/stores/context';
 import { usePageTagsForEditors } from '~/stores/editor';
 import { usePageTagsForEditors } from '~/stores/editor';
@@ -121,6 +122,7 @@ const AdditionalMenuItems = (props: AdditionalMenuItemsProps): JSX.Element => {
       <DropdownItem
       <DropdownItem
         onClick={() => openAccessoriesModal(PageAccessoriesModalContents.ShareLink)}
         onClick={() => openAccessoriesModal(PageAccessoriesModalContents.ShareLink)}
         disabled={isGuestUser || isSharedUser || isLinkSharingDisabled}
         disabled={isGuestUser || isSharedUser || isLinkSharingDisabled}
+        data-testid="open-page-accessories-modal-btn-with-share-link-management-data-tab"
         className="grw-page-control-dropdown-item"
         className="grw-page-control-dropdown-item"
       >
       >
         <span className="grw-page-control-dropdown-icon">
         <span className="grw-page-control-dropdown-icon">
@@ -162,6 +164,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { data: isDrawerMode } = useDrawerMode();
   const { data: isDrawerMode } = useDrawerMode();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: pageId } = useCurrentPageId();
   const { data: pageId } = useCurrentPageId();
+  const { data: currentPathname } = useCurrentPathname();
   const { data: currentUser } = useCurrentUser();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
@@ -344,13 +347,17 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
     templateMenuItemClickHandler, isPageTemplateModalShown,
     templateMenuItemClickHandler, isPageTemplateModalShown,
   ]);
   ]);
 
 
-  if (currentPage == null) {
+  if (currentPathname == null) {
     return <></>;
     return <></>;
   }
   }
 
 
+  const notFoundPage: Partial<IPageHasId> = {
+    path: currentPathname,
+  };
+
   return (
   return (
     <GrowiSubNavigation
     <GrowiSubNavigation
-      page={currentPage}
+      page={currentPage ?? notFoundPage}
       showDrawerToggler={isDrawerMode}
       showDrawerToggler={isDrawerMode}
       showTagLabel={isAbleToShowTagLabel}
       showTagLabel={isAbleToShowTagLabel}
       showPageAuthors={isAbleToShowPageAuthors}
       showPageAuthors={isAbleToShowPageAuthors}

+ 3 - 5
packages/app/src/components/NotFoundPage.tsx

@@ -6,13 +6,12 @@ import dynamic from 'next/dynamic';
 import { DescendantsPageListForCurrentPath } from './DescendantsPageList';
 import { DescendantsPageListForCurrentPath } from './DescendantsPageList';
 import PageListIcon from './Icons/PageListIcon';
 import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
-// import PageTimeline from './PageTimeline';
+import { PageTimeline } from './PageTimeline';
+import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
 
 
 const NotFoundPage = (): JSX.Element => {
 const NotFoundPage = (): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  const CustomNavAndContents = dynamic(() => import('./CustomNavigation/CustomNavAndContents'), { ssr: false });
-
   const navTabMapping = useMemo(() => {
   const navTabMapping = useMemo(() => {
     return {
     return {
       pagelist: {
       pagelist: {
@@ -23,8 +22,7 @@ const NotFoundPage = (): JSX.Element => {
       },
       },
       timeLine: {
       timeLine: {
         Icon: TimeLineIcon,
         Icon: TimeLineIcon,
-        // Content: PageTimeline,
-        Content: () => <></>,
+        Content: PageTimeline,
         i18n: t('Timeline View'),
         i18n: t('Timeline View'),
         index: 1,
         index: 1,
       },
       },

+ 1 - 1
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -16,7 +16,6 @@ import { EditorMode, useEditorMode } from '~/stores/ui';
 import CountBadge from '../Common/CountBadge';
 import CountBadge from '../Common/CountBadge';
 import PageListIcon from '../Icons/PageListIcon';
 import PageListIcon from '../Icons/PageListIcon';
 import { NotCreatablePage } from '../NotCreatablePage';
 import { NotCreatablePage } from '../NotCreatablePage';
-import NotFoundPage from '../NotFoundPage';
 import { Page } from '../Page';
 import { Page } from '../Page';
 // import PageEditor from '../PageEditor';
 // import PageEditor from '../PageEditor';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
@@ -37,6 +36,7 @@ const DisplaySwitcher = (): JSX.Element => {
   const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
   const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
   const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
   const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
   const ContentLinkButtons = dynamic(() => import('../ContentLinkButtons'), { ssr: false });
   const ContentLinkButtons = dynamic(() => import('../ContentLinkButtons'), { ssr: false });
+  const NotFoundPage = dynamic(() => import('../NotFoundPage'), { ssr: false });
 
 
   // get element for smoothScroll
   // get element for smoothScroll
   // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);
   // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);

+ 5 - 1
packages/app/src/components/PageAlert/FixPageGrantAlert.tsx

@@ -76,6 +76,10 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
       return t('fix_page_grant.modal.grant_label.isForbidden');
       return t('fix_page_grant.modal.grant_label.isForbidden');
     }
     }
 
 
+    if (grantData.grant === 1) {
+      return t('fix_page_grant.modal.grant_label.public');
+    }
+
     if (grantData.grant === 4) {
     if (grantData.grant === 4) {
       return t('fix_page_grant.modal.radio_btn.only_me');
       return t('fix_page_grant.modal.radio_btn.only_me');
     }
     }
@@ -87,7 +91,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
       return `${t('fix_page_grant.modal.radio_btn.grant_group')}: (${grantData.grantedGroup.name})`;
       return `${t('fix_page_grant.modal.radio_btn.grant_group')}: (${grantData.grantedGroup.name})`;
     }
     }
 
 
-    throw Error('cannnot get grant label'); // this error can't be throwed
+    throw Error('cannot get grant label'); // this error can't be throwed
   }, [t]);
   }, [t]);
 
 
   const renderGrantDataLabel = useCallback(() => {
   const renderGrantDataLabel = useCallback(() => {

+ 0 - 136
packages/app/src/components/PageTimeline.jsx

@@ -1,136 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-import { useTranslation } from 'next-i18next';
-
-import AppContainer from '~/client/services/AppContainer';
-import PageContainer from '~/client/services/PageContainer';
-import { apiv3Get } from '~/client/util/apiv3-client';
-import { RendererOptions } from '~/services/renderer/renderer';
-import { useTimelineOptions } from '~/stores/renderer';
-
-import RevisionLoader from './Page/RevisionLoader';
-import PaginationWrapper from './PaginationWrapper';
-import { withUnstatedContainers } from './UnstatedUtils';
-
-
-class PageTimeline extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      activePage: 1,
-      totalPageItems: 0,
-      limit: null,
-
-      // TODO: remove after when timeline is implemented with React and inject data with props
-      pages: this.props.pages,
-    };
-
-    this.handlePage = this.handlePage.bind(this);
-  }
-
-
-  async handlePage(selectedPage) {
-    const { appContainer, pageContainer } = this.props;
-    const { path } = pageContainer.state;
-    const page = selectedPage;
-
-    const res = await apiv3Get('/pages/list', { path, page });
-    const totalPageItems = res.data.totalCount;
-    const pages = res.data.pages;
-    const pagingLimit = res.data.limit;
-    this.setState({
-      activePage: selectedPage,
-      totalPageItems,
-      pages,
-      limit: pagingLimit,
-    });
-  }
-
-  UNSAFE_componentWillMount() {
-    const { rendererOptions } = this.props;
-    // initialize GrowiRenderer
-    this.rendererOptions = rendererOptions;
-  }
-
-  async componentDidMount() {
-    await this.handlePage(1);
-    this.setState({
-      activePage: 1,
-    });
-  }
-
-  render() {
-    const { t } = this.props;
-    const { pages } = this.state;
-
-    if (pages == null || pages.length === 0) {
-      return (
-        <div className="mt-2">
-          {/* eslint-disable-next-line react/no-danger */}
-          <p>{t('custom_navigation.no_page_list')}</p>
-        </div>
-      );
-    }
-
-    return (
-      <div>
-        { pages.map((page) => {
-          return (
-            <div className="timeline-body" key={`key-${page._id}`}>
-              <div className="card card-timeline">
-                <div className="card-header"><a href={page.path}>{page.path}</a></div>
-                <div className="card-body">
-                  <RevisionLoader
-                    lazy
-                    rendererOptions={this.rendererOptions}
-                    pageId={page._id}
-                    pagePath={page.path}
-                    revisionId={page.revision}
-                  />
-                </div>
-              </div>
-            </div>
-          );
-        }) }
-        <PaginationWrapper
-          activePage={this.state.activePage}
-          changePage={this.handlePage}
-          totalItemsCount={this.state.totalPageItems}
-          pagingLimit={this.state.limit}
-          align="center"
-        />
-      </div>
-    );
-
-  }
-
-}
-
-PageTimeline.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-  pages: PropTypes.arrayOf(PropTypes.object),
-};
-
-const PageTimelineWrapperFC = (props) => {
-  const { t } = useTranslation();
-  const { data: rendererOptions } = useTimelineOptions();
-
-  if (rendererOptions == null) {
-    return <></>;
-  }
-
-  return <PageTimeline t={t} rendererOptions={rendererOptions} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const PageTimelineWrapper = withUnstatedContainers(PageTimelineWrapperFC, [AppContainer, PageContainer]);
-
-export default PageTimelineWrapper;

+ 78 - 0
packages/app/src/components/PageTimeline.tsx

@@ -0,0 +1,78 @@
+import React, { useEffect, useState, useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { apiv3Get } from '~/client/util/apiv3-client';
+import { IPageHasId } from '~/interfaces/page';
+import { useCurrentPagePath } from '~/stores/context';
+import { useTimelineOptions } from '~/stores/renderer';
+
+import RevisionLoader from './Page/RevisionLoader';
+import PaginationWrapper from './PaginationWrapper';
+
+export const PageTimeline = (): JSX.Element => {
+  const [activePage, setActivePage] = useState(1);
+  const [totalPageItems, setTotalPageItems] = useState(0);
+  const [limit, setLimit] = useState(10);
+  const [pages, setPages] = useState<IPageHasId[] | null>(null);
+
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { t } = useTranslation();
+  const { data: rendererOptions } = useTimelineOptions();
+
+  const handlePage = useCallback(async(selectedPage: number) => {
+    if (currentPagePath == null) { return }
+    const res = await apiv3Get('/pages/list', { path: currentPagePath, selectedPage });
+    setTotalPageItems(res.data.totalCount);
+    setPages(res.data.pages);
+    setLimit(res.data.limit);
+    setActivePage(selectedPage);
+  }, [currentPagePath]);
+
+  useEffect(() => {
+    handlePage(1);
+  }, [handlePage]);
+
+  if (rendererOptions == null) {
+    return <></>;
+  }
+
+  if (pages == null || pages.length === 0) {
+    return (
+      <div className="mt-2">
+        {/* eslint-disable-next-line react/no-danger */}
+        <p>{t('custom_navigation.no_page_list')}</p>
+      </div>
+    );
+  }
+
+  return (
+    <div>
+      { pages.map((page) => {
+        return (
+          <div className="timeline-body" key={`key-${page._id}`}>
+            <div className="card card-timeline">
+              <div className="card-header"><a href={page.path}>{page.path}</a></div>
+              <div className="card-body">
+                <RevisionLoader
+                  lazy
+                  rendererOptions={rendererOptions}
+                  pageId={page._id}
+                  pagePath={page.path}
+                  revisionId={page.revision}
+                />
+              </div>
+            </div>
+          </div>
+        );
+      }) }
+      <PaginationWrapper
+        activePage={activePage}
+        changePage={handlePage}
+        totalItemsCount={totalPageItems}
+        pagingLimit={limit}
+        align="center"
+      />
+    </div>
+  );
+};

+ 1 - 1
packages/app/src/components/ShareLink/ShareLink.jsx

@@ -86,7 +86,7 @@ class ShareLink extends React.Component {
     const { t } = this.props;
     const { t } = this.props;
 
 
     return (
     return (
-      <div className="container p-0">
+      <div className="container p-0" data-testid="share-link-management">
         <h3 className="grw-modal-head d-flex pb-2">
         <h3 className="grw-modal-head d-flex pb-2">
           { t('share_links.share_link_list') }
           { t('share_links.share_link_list') }
           <button className="btn btn-danger ml-auto " type="button" onClick={this.deleteAllLinksButtonHandler}>{t('delete_all')}</button>
           <button className="btn btn-danger ml-auto " type="button" onClick={this.deleteAllLinksButtonHandler}>{t('delete_all')}</button>

+ 2 - 0
packages/app/src/interfaces/activity.ts

@@ -376,6 +376,8 @@ export const SmallActionGroup = {
   ACTION_USER_LOGOUT,
   ACTION_USER_LOGOUT,
   ACTION_PAGE_CREATE,
   ACTION_PAGE_CREATE,
   ACTION_PAGE_DELETE,
   ACTION_PAGE_DELETE,
+  ACTION_PAGE_DELETE_COMPLETELY,
+  ACTION_PAGE_EMPTY_TRASH,
 } as const;
 } as const;
 
 
 // SmallActionGroup + Action by all General Users - PAGE_VIEW
 // SmallActionGroup + Action by all General Users - PAGE_VIEW

+ 3 - 4
packages/app/src/interfaces/in-app-notification.ts

@@ -1,6 +1,5 @@
-import { Types } from 'mongoose';
-import { IUser } from './user';
 import { IPage } from './page';
 import { IPage } from './page';
+import { IUser } from './user';
 
 
 export enum InAppNotificationStatuses {
 export enum InAppNotificationStatuses {
   STATUS_UNREAD = 'UNREAD',
   STATUS_UNREAD = 'UNREAD',
@@ -54,7 +53,7 @@ export interface ISubscribeRule {
   name: subscribeRuleNames;
   name: subscribeRuleNames;
   isEnabled: boolean;
   isEnabled: boolean;
 }
 }
-export interface IInAppNotificationSettings {
-  userId: Types.ObjectId;
+export interface IInAppNotificationSettings<UserID> {
+  userId: UserID | string;
   subscribeRules: ISubscribeRule[];
   subscribeRules: ISubscribeRule[];
 }
 }

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:make-email-unique');
 const logger = loggerFactory('growi:migrate:make-email-unique');

+ 1 - 1
packages/app/src/migrations/20180927102719-init-serverurl.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:init-serverurl');
 const logger = loggerFactory('growi:migrate:init-serverurl');

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

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
 import getPageModel from '~/server/models/page';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
 
 

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

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:abolish-crowi-classic-auth');
 const logger = loggerFactory('growi:migrate:abolish-crowi-classic-auth');

+ 1 - 1
packages/app/src/migrations/20190618104011-add-config-app-installed.js

@@ -1,8 +1,8 @@
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:add-config-app-installed');
 const logger = loggerFactory('growi:migrate:add-config-app-installed');

+ 2 - 2
packages/app/src/migrations/20190619055421-adjust-page-grant.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
 import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:adjust-page-grant');
 const logger = loggerFactory('growi:migrate:adjust-page-grant');
 
 

+ 2 - 2
packages/app/src/migrations/20190624110950-fill-last-update-user.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
 import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
 
 

+ 2 - 2
packages/app/src/migrations/20190629193445-make-root-page-public.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
 import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:make-root-page-public');
 const logger = loggerFactory('growi:migrate:make-root-page-public');
 
 

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:drop-configs-indices');
 const logger = loggerFactory('growi:migrate:drop-configs-indices');

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:drop-pages-indices');
 const logger = loggerFactory('growi:migrate:drop-pages-indices');

+ 3 - 2
packages/app/src/migrations/20191126173016-adjust-pages-path.js

@@ -1,7 +1,8 @@
+import { pathUtils } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
-import { pathUtils, getMongoUri, mongoOptions } from '@growi/core';
-import getPageModel from '~/server/models/page';
 
 
+import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:adjust-pages-path');
 const logger = loggerFactory('growi:migrate:adjust-pages-path');

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:drop-wrong-index-of-page-tag-relation');
 const logger = loggerFactory('growi:migrate:drop-wrong-index-of-page-tag-relation');

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:remove-deleteduser-from-relationgroup');
 const logger = loggerFactory('growi:migrate:remove-deleteduser-from-relationgroup');

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

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
 const logger = loggerFactory('growi:migrate:remove-crowi-lauout');

+ 1 - 1
packages/app/src/migrations/20200512005851-remove-behavior-type.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:remove-behavior-type');
 const logger = loggerFactory('growi:migrate:remove-behavior-type');

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

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:update-theme-color-for-dark');
 const logger = loggerFactory('growi:migrate:update-theme-color-for-dark');

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

@@ -1,8 +1,8 @@
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:normalize-locale-id');
 const logger = loggerFactory('growi:migrate:normalize-locale-id');

+ 1 - 1
packages/app/src/migrations/20200827045151-remove-layout-setting.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:remove-layout-setting');
 const logger = loggerFactory('growi:migrate:remove-layout-setting');

+ 1 - 1
packages/app/src/migrations/20200828024025-copy-aws-setting.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:remove-layout-setting');
 const logger = loggerFactory('growi:migrate:remove-layout-setting');

+ 1 - 1
packages/app/src/migrations/20200901034313-update-mail-transmission.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:update-mail-transmission');
 const logger = loggerFactory('growi:migrate:update-mail-transmission');

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

@@ -1,7 +1,6 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
-
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

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

@@ -1,7 +1,6 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
-
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

+ 2 - 2
packages/app/src/migrations/20210420160380-convert-double-to-date.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
 import getPageModel from '~/server/models/page';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
 const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
 
 

+ 1 - 1
packages/app/src/migrations/20210830074539-update-configs-for-slackbot.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:update-configs-for-slackbot');
 const logger = loggerFactory('growi:migrate:update-configs-for-slackbot');

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:slack-app-integration-set-default-value');
 const logger = loggerFactory('growi:migrate:slack-app-integration-set-default-value');

+ 2 - 2
packages/app/src/migrations/20210913153942-migrate-slack-app-integration-schema.js

@@ -1,7 +1,7 @@
-import mongoose from 'mongoose';
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
+import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

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

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
 import getPageModel from '~/server/models/page';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:add-column-is-trashed');
 const logger = loggerFactory('growi:migrate:add-column-is-trashed');
 
 

+ 1 - 1
packages/app/src/migrations/20211005120030-slack-app-integration-rename-keys.js

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

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

@@ -1,8 +1,8 @@
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

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

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
-import NamedQuery from '~/server/models/named-query';
 import { SearchDelegatorName } from '~/interfaces/named-query';
 import { SearchDelegatorName } from '~/interfaces/named-query';
+import NamedQuery from '~/server/models/named-query';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 

+ 4 - 3
packages/app/src/migrations/20211227060705-revision-path-to-page-id-schema-migration.js

@@ -1,11 +1,12 @@
-import mongoose from 'mongoose';
 import { Writable } from 'stream';
 import { Writable } from 'stream';
+
+import mongoose from 'mongoose';
 import streamToPromise from 'stream-to-promise';
 import streamToPromise from 'stream-to-promise';
 
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
 import getPageModel from '~/server/models/page';
 import { createBatchStream } from '~/server/util/batch-stream';
 import { createBatchStream } from '~/server/util/batch-stream';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 
 
 
 const logger = loggerFactory('growi:migrate:revision-path-to-page-id-schema-migration');
 const logger = loggerFactory('growi:migrate:revision-path-to-page-id-schema-migration');

+ 1 - 1
packages/app/src/migrations/20220131001218-convert-redirect-to-pages-to-page-redirect-documents.js

@@ -1,9 +1,9 @@
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 // eslint-disable-next-line import/no-named-as-default
 // eslint-disable-next-line import/no-named-as-default
 import PageRedirectModel from '~/server/models/page-redirect';
 import PageRedirectModel from '~/server/models/page-redirect';
 import { createBatchStream } from '~/server/util/batch-stream';
 import { createBatchStream } from '~/server/util/batch-stream';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:convert-redirect-to-pages-to-page-redirect-documents');
 const logger = loggerFactory('growi:migrate:convert-redirect-to-pages-to-page-redirect-documents');

+ 2 - 3
packages/app/src/migrations/20220311011114-convert-page-delete-config.js

@@ -1,11 +1,10 @@
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 
 
-import ConfigModel from '~/server/models/config';
 import {
 import {
   PageRecursiveDeleteConfigValue, PageRecursiveDeleteCompConfigValue,
   PageRecursiveDeleteConfigValue, PageRecursiveDeleteCompConfigValue,
 } from '~/interfaces/page-delete-config';
 } from '~/interfaces/page-delete-config';
-
+import ConfigModel from '~/server/models/config';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:convert-page-delete-config');
 const logger = loggerFactory('growi:migrate:convert-page-delete-config');

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

@@ -1,6 +1,6 @@
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:set-sparse-option-to-slack-member-id');
 const logger = loggerFactory('growi:migrate:set-sparse-option-to-slack-member-id');

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

@@ -1,8 +1,8 @@
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
 import { AttachmentType } from '~/server/interfaces/attachment';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import attachmentModel from '~/server/models/attachment';
 import attachmentModel from '~/server/models/attachment';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:add-attachment-type-to-existing-attachments');
 const logger = loggerFactory('growi:migrate:add-attachment-type-to-existing-attachments');

+ 2 - 1
packages/app/src/pages/_app.page.tsx

@@ -10,7 +10,8 @@ import '~/styles/style-next.scss';
 // import '~/styles/theme/default.scss';
 // import '~/styles/theme/default.scss';
 // import InterceptorManager from '~/service/interceptor-manager';
 // import InterceptorManager from '~/service/interceptor-manager';
 
 
-import * as nextI18nConfig from '../next-i18next.config';
+import * as nextI18nConfig from '^/config/next-i18next.config';
+
 import { useI18nextHMR } from '../services/i18next-hmr';
 import { useI18nextHMR } from '../services/i18next-hmr';
 import {
 import {
   useAppTitle, useConfidential, useGrowiTheme, useGrowiVersion, useSiteUrl,
   useAppTitle, useConfidential, useGrowiTheme, useGrowiVersion, useSiteUrl,

+ 2 - 1
packages/app/src/pages/utils/commons.ts

@@ -2,10 +2,11 @@ import { DevidedPagePath, Lang } from '@growi/core';
 import { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { SSRConfig, UserConfig } from 'next-i18next';
 import { SSRConfig, UserConfig } from 'next-i18next';
 
 
+import * as nextI18NextConfig from '^/config/next-i18next.config';
+
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { GrowiThemes } from '~/interfaces/theme';
 import { GrowiThemes } from '~/interfaces/theme';
 
 
-import * as nextI18NextConfig from '../../next-i18next.config';
 
 
 export type CommonProps = {
 export type CommonProps = {
   namespacesRequired: string[], // i18next
   namespacesRequired: string[], // i18next

+ 13 - 4
packages/app/src/pages/utils/objectid-transformer.ts

@@ -1,11 +1,20 @@
-import { Types as MongooseTypes } from 'mongoose';
+// !!! Do NOT import 'mongoose' to reduce bundle size !!!
+import ObjectId from 'bson-objectid';
 import superjson from 'superjson';
 import superjson from 'superjson';
 
 
 export const registerTransformerForObjectId = (): void => {
 export const registerTransformerForObjectId = (): void => {
-  superjson.registerCustom<MongooseTypes.ObjectId|string, string>(
+  superjson.registerCustom<ObjectId|string, string>(
     {
     {
-      isApplicable: (v): v is MongooseTypes.ObjectId => v instanceof MongooseTypes.ObjectId,
-      serialize: v => (v instanceof MongooseTypes.ObjectId ? v.toHexString() : v),
+      isApplicable: (v): v is ObjectId => {
+        if (typeof v === 'string') {
+          return ObjectId.isValid(v);
+        }
+        if (typeof v.toHexString === 'function') {
+          return ObjectId.isValid(v.toHexString());
+        }
+        return false;
+      },
+      serialize: v => (typeof v === 'string' ? v : v.toHexString()),
       deserialize: v => v,
       deserialize: v => v,
     },
     },
     'ObjectidTransformer',
     'ObjectidTransformer',

+ 4 - 2
packages/app/src/server/console.js

@@ -1,8 +1,10 @@
-const repl = require('repl');
 const fs = require('fs');
 const fs = require('fs');
 const path = require('path');
 const path = require('path');
+const repl = require('repl');
+
 const mongoose = require('mongoose');
 const mongoose = require('mongoose');
-const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('@growi/core');
+
+const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
 
 
 const models = require('./models');
 const models = require('./models');
 
 

+ 2 - 1
packages/app/src/server/crowi/dev.js

@@ -1,6 +1,7 @@
 import path from 'path';
 import path from 'path';
 
 
-import { i18n } from '~/next-i18next.config';
+import { i18n } from '^/config/next-i18next.config';
+
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import nextFactory from '../routes/next';
 import nextFactory from '../routes/next';

+ 2 - 1
packages/app/src/server/crowi/express-init.js

@@ -1,7 +1,8 @@
 import csrf from 'csurf';
 import csrf from 'csurf';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { i18n, localePath } from '~/next-i18next.config';
+import { i18n, localePath } from '^/config/next-i18next.config';
+
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:crowi:express-init');
 const logger = loggerFactory('growi:crowi:express-init');

+ 1 - 1
packages/app/src/server/crowi/index.js

@@ -3,7 +3,6 @@ import http from 'http';
 import path from 'path';
 import path from 'path';
 
 
 import { createTerminus } from '@godaddy/terminus';
 import { createTerminus } from '@godaddy/terminus';
-import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 import next from 'next';
 import next from 'next';
 
 
@@ -29,6 +28,7 @@ import PageOperationService from '../service/page-operation';
 import SearchService from '../service/search';
 import SearchService from '../service/search';
 import { SlackIntegrationService } from '../service/slack-integration';
 import { SlackIntegrationService } from '../service/slack-integration';
 import { UserNotificationService } from '../service/user-notification';
 import { UserNotificationService } from '../service/user-notification';
+import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '../util/mongoose-utils';
 
 
 const logger = loggerFactory('growi:crowi');
 const logger = loggerFactory('growi:crowi');
 const httpErrorHandler = require('../middlewares/http-error-handler');
 const httpErrorHandler = require('../middlewares/http-error-handler');

+ 2 - 13
packages/app/src/server/models/activity.ts

@@ -1,4 +1,3 @@
-import { getOrCreateModel, getModelSafely } from '@growi/core';
 import {
 import {
   Types, Document, Model, Schema,
   Types, Document, Model, Schema,
 } from 'mongoose';
 } from 'mongoose';
@@ -11,6 +10,8 @@ import {
 } from '~/interfaces/activity';
 } from '~/interfaces/activity';
 
 
 import loggerFactory from '../../utils/logger';
 import loggerFactory from '../../utils/logger';
+import { getOrCreateModel, getModelSafely } from '../util/mongoose-utils';
+
 
 
 import Subscription from './subscription';
 import Subscription from './subscription';
 
 
@@ -126,18 +127,6 @@ activitySchema.statics.updateByParameters = async function(activityId: string, p
   return activity;
   return activity;
 };
 };
 
 
-activitySchema.statics.getPaginatedActivity = async function(limit: number, offset: number, query) {
-  const paginateResult = await this.paginate(
-    query,
-    {
-      limit,
-      offset,
-      sort: { createdAt: -1 },
-    },
-  );
-  return paginateResult;
-};
-
 activitySchema.statics.findSnapshotUsernamesByUsernameRegexWithTotalCount = async function(
 activitySchema.statics.findSnapshotUsernamesByUsernameRegexWithTotalCount = async function(
     q: string, option: { sortOpt: number | string, offset: number, limit: number},
     q: string, option: { sortOpt: number | string, offset: number, limit: number},
 ): Promise<{usernames: string[], totalCount: number}> {
 ): Promise<{usernames: string[], totalCount: number}> {

+ 2 - 1
packages/app/src/server/models/config.ts

@@ -1,9 +1,10 @@
-import { getOrCreateModel } from '@growi/core';
 import { Types, Schema } from 'mongoose';
 import { Types, Schema } from 'mongoose';
 import uniqueValidator from 'mongoose-unique-validator';
 import uniqueValidator from 'mongoose-unique-validator';
 
 
 import { GrowiThemes } from '~/interfaces/theme';
 import { GrowiThemes } from '~/interfaces/theme';
 
 
+import { getOrCreateModel } from '../util/mongoose-utils';
+
 
 
 export interface Config {
 export interface Config {
   _id: Types.ObjectId;
   _id: Types.ObjectId;

+ 2 - 1
packages/app/src/server/models/editor-settings.ts

@@ -1,10 +1,11 @@
-import { getOrCreateModel } from '@growi/core';
 import {
 import {
   Schema, Model, Document,
   Schema, Model, Document,
 } from 'mongoose';
 } from 'mongoose';
 
 
 import { IEditorSettings, ITextlintSettings } from '~/interfaces/editor-settings';
 import { IEditorSettings, ITextlintSettings } from '~/interfaces/editor-settings';
 
 
+import { getOrCreateModel } from '../util/mongoose-utils';
+
 
 
 export interface EditorSettingsDocument extends IEditorSettings, Document {
 export interface EditorSettingsDocument extends IEditorSettings, Document {
   userId: Schema.Types.ObjectId,
   userId: Schema.Types.ObjectId,

+ 6 - 3
packages/app/src/server/models/in-app-notification-settings.ts

@@ -1,9 +1,12 @@
-import { getOrCreateModel } from '@growi/core';
-import { Schema, Model, Document } from 'mongoose';
+import {
+  Schema, Model, Document, Types,
+} from 'mongoose';
 
 
 import { IInAppNotificationSettings, subscribeRuleNames } from '~/interfaces/in-app-notification';
 import { IInAppNotificationSettings, subscribeRuleNames } from '~/interfaces/in-app-notification';
 
 
-export interface InAppNotificationSettingsDocument extends IInAppNotificationSettings, Document {}
+import { getOrCreateModel } from '../util/mongoose-utils';
+
+export interface InAppNotificationSettingsDocument extends IInAppNotificationSettings<Types.ObjectId>, Document {}
 export type InAppNotificationSettingsModel = Model<InAppNotificationSettingsDocument>
 export type InAppNotificationSettingsModel = Model<InAppNotificationSettingsDocument>
 
 
 const inAppNotificationSettingsSchema = new Schema<InAppNotificationSettingsDocument, InAppNotificationSettingsModel>({
 const inAppNotificationSettingsSchema = new Schema<InAppNotificationSettingsDocument, InAppNotificationSettingsModel>({

+ 2 - 1
packages/app/src/server/models/in-app-notification.ts

@@ -1,4 +1,3 @@
-import { getOrCreateModel } from '@growi/core';
 import {
 import {
   Types, Document, Schema, Model,
   Types, Document, Schema, Model,
 } from 'mongoose';
 } from 'mongoose';
@@ -7,6 +6,8 @@ import mongoosePaginate from 'mongoose-paginate-v2';
 import { AllSupportedTargetModels, AllSupportedActions } from '~/interfaces/activity';
 import { AllSupportedTargetModels, AllSupportedActions } from '~/interfaces/activity';
 import { InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 import { InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 
 
+import { getOrCreateModel } from '../util/mongoose-utils';
+
 import { ActivityDocument } from './activity';
 import { ActivityDocument } from './activity';
 
 
 
 

+ 4 - 2
packages/app/src/server/models/named-query.ts

@@ -4,10 +4,12 @@ import mongoose, {
   Schema, Model, Document,
   Schema, Model, Document,
 } from 'mongoose';
 } from 'mongoose';
 
 
-import { getOrCreateModel } from '@growi/core';
-import loggerFactory from '../../utils/logger';
 import { INamedQuery, SearchDelegatorName } from '~/interfaces/named-query';
 import { INamedQuery, SearchDelegatorName } from '~/interfaces/named-query';
 
 
+import loggerFactory from '../../utils/logger';
+import { getOrCreateModel } from '../util/mongoose-utils';
+
+
 const logger = loggerFactory('growi:models:named-query');
 const logger = loggerFactory('growi:models:named-query');
 
 
 export interface NamedQueryDocument extends INamedQuery, Document {}
 export interface NamedQueryDocument extends INamedQuery, Document {}

+ 2 - 1
packages/app/src/server/models/page-operation.ts

@@ -1,4 +1,3 @@
-import { getOrCreateModel } from '@growi/core';
 import { addSeconds } from 'date-fns';
 import { addSeconds } from 'date-fns';
 import mongoose, {
 import mongoose, {
   Schema, Model, Document, QueryOptions, FilterQuery,
   Schema, Model, Document, QueryOptions, FilterQuery,
@@ -8,8 +7,10 @@ import {
   IPageForResuming, IUserForResuming, IOptionsForResuming,
   IPageForResuming, IUserForResuming, IOptionsForResuming,
 } from '~/server/models/interfaces/page-operation';
 } from '~/server/models/interfaces/page-operation';
 
 
+
 import loggerFactory from '../../utils/logger';
 import loggerFactory from '../../utils/logger';
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
 import { ObjectIdLike } from '../interfaces/mongoose-utils';
+import { getOrCreateModel } from '../util/mongoose-utils';
 
 
 const TIME_TO_ADD_SEC = 10;
 const TIME_TO_ADD_SEC = 10;
 
 

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