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

Merge pull request #4418 from weseek/master

Release slackbot-proxy 4.4.7-slackbot-proxy.1
Yuki Takei 4 лет назад
Родитель
Сommit
3d95debf0f
100 измененных файлов с 570 добавлено и 396 удалено
  1. 9 9
      .devcontainer/Dockerfile
  2. 1 0
      .gitattributes
  3. 8 0
      .github/workflows/ci.yml
  4. 2 0
      .github/workflows/release-rc.yml
  5. 1 1
      .github/workflows/release-slackbot-proxy.yml
  6. 6 3
      .github/workflows/release.yml
  7. 12 1
      CHANGELOG.md
  8. 11 0
      THIRD-PARTY-NOTICES.md
  9. 1 1
      lerna.json
  10. 9 6
      package.json
  11. 2 0
      packages/app/.env.development
  12. 1 0
      packages/app/.env.production
  13. 2 1
      packages/app/.eslintrc.js
  14. 1 1
      packages/app/.gitignore
  15. 1 12
      packages/app/bin/cdn/cdn-resources-downloader.ts
  16. 0 1
      packages/app/config/cdn.js
  17. 2 2
      packages/app/config/webpack.prod.js
  18. 6 11
      packages/app/docker/Dockerfile
  19. 6 0
      packages/app/docker/Dockerfile.dockerignore
  20. 2 2
      packages/app/docker/README.md
  21. 1 1
      packages/app/docker/docker-entrypoint.sh
  22. 6 0
      packages/app/docker/nocdn/.env.production.local
  23. 0 5
      packages/app/docker/nocdn/env.prod.js
  24. 21 5
      packages/app/jest.config.js
  25. 8 5
      packages/app/migrate-mongo-config.js
  26. 16 13
      packages/app/package.json
  27. 3 0
      packages/app/public/static/dict/base.dat.gz
  28. 3 0
      packages/app/public/static/dict/cc.dat.gz
  29. 3 0
      packages/app/public/static/dict/check.dat.gz
  30. 3 0
      packages/app/public/static/dict/tid.dat.gz
  31. 3 0
      packages/app/public/static/dict/tid_map.dat.gz
  32. 3 0
      packages/app/public/static/dict/tid_pos.dat.gz
  33. 3 0
      packages/app/public/static/dict/unk.dat.gz
  34. 3 0
      packages/app/public/static/dict/unk_char.dat.gz
  35. 3 0
      packages/app/public/static/dict/unk_compat.dat.gz
  36. 3 0
      packages/app/public/static/dict/unk_invoke.dat.gz
  37. 3 0
      packages/app/public/static/dict/unk_map.dat.gz
  38. 3 0
      packages/app/public/static/dict/unk_pos.dat.gz
  39. 0 86
      packages/app/resource/cdn-manifests.js
  40. 1 1
      packages/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  41. 4 4
      packages/app/src/components/Me/EditorSettings.tsx
  42. 2 5
      packages/app/src/components/PageEditor/CodeMirrorEditor.jsx
  43. 1 1
      packages/app/src/components/Sidebar/RecentChanges.jsx
  44. 2 3
      packages/app/src/migrations/20180926134048-make-email-unique.js
  45. 3 3
      packages/app/src/migrations/20180927102719-init-serverurl.js
  46. 3 4
      packages/app/src/migrations/20181019114028-abolish-page-group-relation.js
  47. 2 2
      packages/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js
  48. 3 4
      packages/app/src/migrations/20190618104011-add-config-app-installed.js
  49. 2 2
      packages/app/src/migrations/20190619055421-adjust-page-grant.js
  50. 2 2
      packages/app/src/migrations/20190624110950-fill-last-update-user.js
  51. 2 2
      packages/app/src/migrations/20190629193445-make-root-page-public.js
  52. 2 2
      packages/app/src/migrations/20191102223900-drop-configs-indices.js
  53. 2 2
      packages/app/src/migrations/20191102223901-drop-pages-indices.js
  54. 3 3
      packages/app/src/migrations/20191126173016-adjust-pages-path.js
  55. 2 2
      packages/app/src/migrations/20191127023815-drop-wrong-index-of-page-tag-relation.js
  56. 2 3
      packages/app/src/migrations/20200402160380-remove-deleteduser-from-relationgroup.js
  57. 2 2
      packages/app/src/migrations/20200420160390-remove-crowi-layout.js
  58. 3 3
      packages/app/src/migrations/20200512005851-remove-behavior-type.js
  59. 2 2
      packages/app/src/migrations/20200514001356-update-theme-color-for-dark.js
  60. 2 3
      packages/app/src/migrations/20200620203632-normalize-locale-id.js
  61. 3 3
      packages/app/src/migrations/20200827045151-remove-layout-setting.js
  62. 3 3
      packages/app/src/migrations/20200828024025-copy-aws-setting.js
  63. 3 3
      packages/app/src/migrations/20200901034313-update-mail-transmission.js
  64. 2 2
      packages/app/src/migrations/20200901034314-update-mail-transmission-fix.js
  65. 3 3
      packages/app/src/migrations/20200903080025-remove-timeline-type.js.js
  66. 3 3
      packages/app/src/migrations/20200915035234-rename-s3-config.js
  67. 2 3
      packages/app/src/migrations/20210420160380-convert-double-to-date.js
  68. 35 44
      packages/app/src/migrations/20210830074539-update-configs-for-slackbot.js
  69. 3 4
      packages/app/src/migrations/20210906194521-slack-app-integration-set-default-value.js
  70. 57 43
      packages/app/src/migrations/20210913153942-migrate-slack-app-integration-schema.js
  71. 1 1
      packages/app/src/server/console.js
  72. 28 5
      packages/app/src/server/crowi/index.js
  73. 1 1
      packages/app/src/server/models/config.ts
  74. 1 1
      packages/app/src/server/models/editor-settings.ts
  75. 1 1
      packages/app/src/server/models/password-reset-order.ts
  76. 1 1
      packages/app/src/server/models/update-post.ts
  77. 2 3
      packages/app/src/server/routes/apiv3/slack-integration-settings.js
  78. 5 2
      packages/app/src/server/service/socket-io.js
  79. 0 40
      packages/app/src/test/config/migrate.test.js
  80. 0 0
      packages/app/src/test/integration/crowi/crowi.test.js
  81. 1 1
      packages/app/src/test/integration/global-setup.js
  82. 0 0
      packages/app/src/test/integration/global-teardown.js
  83. 0 0
      packages/app/src/test/integration/middlewares/access-token-parser.test.js
  84. 0 0
      packages/app/src/test/integration/middlewares/login-required.test.js
  85. 122 0
      packages/app/src/test/integration/migrations/20210913153942-migrate-slack-app-integration-schema.test.ts
  86. 0 0
      packages/app/src/test/integration/models/config.test.js
  87. 0 0
      packages/app/src/test/integration/models/page.test.js
  88. 0 0
      packages/app/src/test/integration/models/share-link.test.js
  89. 0 0
      packages/app/src/test/integration/models/update-post.test.js
  90. 0 0
      packages/app/src/test/integration/models/user.test.js
  91. 0 0
      packages/app/src/test/integration/service/acl.test.js
  92. 0 0
      packages/app/src/test/integration/service/config-manager.test.js
  93. 2 2
      packages/app/src/test/integration/service/page.test.js
  94. 0 0
      packages/app/src/test/integration/service/passport.test.js
  95. 0 0
      packages/app/src/test/integration/service/search-delegator/searchbox.test.js
  96. 1 5
      packages/app/src/test/integration/setup-crowi.js
  97. 1 1
      packages/app/src/test/integration/setup.js
  98. 0 0
      packages/app/src/test/integration/utils/slack-legacy.test.js
  99. 0 0
      packages/app/src/test/unit/middlewares/safe-redirect.test.js
  100. 71 0
      packages/app/src/test/unit/migrate-mongo-config.test.js

+ 9 - 9
.devcontainer/Dockerfile

@@ -29,15 +29,15 @@ RUN chown -R $USER_UID:$USER_GID /home/$USERNAME /workspace;
 # * any needed dependencies after executing "apt-get update". *
 # * See https://docs.docker.com/engine/reference/builder/#run *
 # *************************************************************
-# ENV DEBIAN_FRONTEND=noninteractive
-# RUN apt-get update \
-#    && apt-get -y install --no-install-recommends <your-package-list-here> \
-#    #
-#    # Clean up
-#    && apt-get autoremove -y \
-#    && apt-get clean -y \
-#    && rm -rf /var/lib/apt/lists/*
-# ENV DEBIAN_FRONTEND=dialog
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get update \
+   && apt-get -y install --no-install-recommends git-lfs \
+
+   # Clean up
+   && apt-get autoremove -y \
+   && apt-get clean -y \
+   && rm -rf /var/lib/apt/lists/*
+ENV DEBIAN_FRONTEND=dialog
 
 # Uncomment to default to non-root user
 # USER $USER_UID

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+*.gz filter=lfs diff=lfs merge=lfs -text

+ 8 - 0
.github/workflows/ci.yml

@@ -208,6 +208,8 @@ jobs:
     - name: Build
       run: |
         yarn lerna run build
+      env:
+        ANALYZE_BUNDLE_SIZE: ${{ matrix.node-version == '14.x' }}
     - name: lerna bootstrap --production
       run: |
         npx lerna bootstrap -- --production
@@ -235,6 +237,12 @@ jobs:
       env:
         MONGO_URI: mongodb://localhost:${{ job.services.mongodb36.ports['27017'] }}/growi-${{ steps.getdbname.outputs.suffix }}
 
+    - name: Upload report as artifact
+      uses: actions/upload-artifact@v2
+      with:
+        name: Bundle Analyzing Report
+        path: packages/app/report/bundle-analyzer.html
+
     - name: Slack Notification
       uses: weseek/ghaction-slack-notification@master
       if: failure()

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

@@ -13,6 +13,8 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
+      with:
+        lfs: true
 
     - name: Retrieve information from package.json
       uses: myrotvorets/info-from-package-json-action@1.1.0

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

@@ -124,7 +124,7 @@ jobs:
       uses: myrotvorets/info-from-package-json-action@1.1.0
       id: package-json
       with:
-        workingDir: packages/slackbot
+        workingDir: packages/slackbot-proxy
 
     - name: Commit
       uses: github-actions-x/commit@v2.8

+ 6 - 3
.github/workflows/release.yml

@@ -34,7 +34,7 @@ jobs:
 
     - name: Bump versions
       run: |
-        node ./bin/github-actions/bump-versions -i patch
+        yarn bump-versions:patch
         sh ./packages/app/bin/github-actions/update-readme.sh
 
     - name: Retrieve information from package.json
@@ -95,8 +95,8 @@ jobs:
 
     - name: Bump versions for next RC
       run: |
-        node ./bin/github-actions/bump-versions -i prerelease
-        node ./bin/github-actions/bump-versions -i prerelease -d packages/slackbot-proxy --preid slackbot-proxy --update-dependencies false
+        yarn bump-versions:rc
+        yarn bump-versions:slackbot-proxy
 
     - name: Retrieve information from package.json
       uses: myrotvorets/info-from-package-json-action@1.1.0
@@ -134,6 +134,7 @@ jobs:
     - uses: actions/checkout@v2
       with:
         ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
+        lfs: true
 
     - name: Setup suffix
       id: suffix
@@ -184,6 +185,8 @@ jobs:
         file: ./packages/app/docker/Dockerfile
         platforms: linux/amd64
         push: true
+        build-args: |
+          flavor=${{ matrix.flavor }}
         cache-from: type=local,src=/tmp/.buildx-cache
         cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new
         tags: ${{ steps.meta.outputs.tags }}

+ 12 - 1
CHANGELOG.md

@@ -1,9 +1,20 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v4.4.5...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v4.4.6...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v4.4.6](https://github.com/weseek/growi/compare/v4.4.5...v4.4.6) - 2021-09-24
+
+### 🚀 Improvement
+
+- imprv: Slackbot response flow (#4296) @yuki-takei
+- imprv(slackbot-proxy): Show version on the top page (#4342) @yuto-oweseek
+
+### 🧰 Maintenance
+
+- support(slackbot-proxy): Bump slackbot proxy version independentry (#4385) @yuki-takei
+
 ## [v4.4.5](https://github.com/weseek/growi/compare/v4.4.4...v4.4.5) - 2021-09-23
 
 ### 🐛 Bug Fixes

+ 11 - 0
THIRD-PARTY-NOTICES.md

@@ -17,6 +17,7 @@ https://github.com/weseek/growi.
 3. Microsoft/vscode (https://github.com/Microsoft/vscode)
 4. stephenhutchings/typicons.font (https://github.com/stephenhutchings/typicons.font)
 5. EmojiOne Version 3 (https://github.com/joypixels/emojione/tree/v3.1.1)
+6. Kuromoji.js (https://github.com/takuyaa/kuromoji.js)
 
 
 License Notice for Apache License, Version 2.0 Derivative Works
@@ -108,3 +109,13 @@ https://creativecommons.org/licenses/by/4.0/
 ```
 author: "EmojiOne <ryan@emojione.com> (http://emojione.com)"
 ```
+
+
+License Notice for Kuromoji.js
+------------------------
+
+https://github.com/takuyaa/kuromoji.js/blob/master/LICENSE-2.0.txt
+
+```
+author: "Takuya Asano <takuya.a@gmail.com>"
+```

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
   "npmClient": "yarn",
   "useWorkspaces": true,
-  "version": "4.4.6-RC.0",
+  "version": "4.4.7-RC.0",
   "packages": [
     "packages/*"
   ]

+ 9 - 6
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "4.4.6-RC.0",
+  "version": "4.4.7-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -35,6 +35,9 @@
     "app:server": "yarn lerna run server --scope @growi/app",
     "slackbot-proxy:build": "yarn lerna run build --scope @growi/slackbot-proxy --scope @growi/slack",
     "slackbot-proxy:server": "yarn lerna run start:prod --scope @growi/slackbot-proxy",
+    "bump-versions:patch": "node ./bin/github-actions/bump-versions -i patch",
+    "bump-versions:rc": "node ./bin/github-actions/bump-versions -i prerelease",
+    "bump-versions:slackbot-proxy": "node ./bin/github-actions/bump-versions -i prerelease -d packages/slackbot-proxy --preid slackbot-proxy --update-dependencies false",
     "//// scripts for backward compatibility": "",
     "build:prod": "echo !!! CAUTION !!! ==> The script 'build:prod' is deprecated. Use 'yarn app:build' instead. && yarn app:build",
     "server:prod": "echo !!! CAUTION !!! ==> The script 'server:prod' is deprecated. Use 'yarn app:build' instead. && yarn app:server"
@@ -43,10 +46,7 @@
     "cross-env": "^7.0.0",
     "dotenv-flow": "^3.2.0",
     "npm-run-all": "^4.1.5",
-    "ts-node": "^9.1.1",
-    "tsconfig-paths": "^3.9.0",
-    "tslib": "^2.3.1",
-    "typescript": "^4.2.3"
+    "tslib": "^2.3.1"
   },
   "devDependencies": {
     "@types/jest": "^26.0.22",
@@ -67,7 +67,10 @@
     "lerna": "^4.0.0",
     "rewire": "^5.0.0",
     "shipjs": "^0.23.3",
-    "ts-jest": "^27.0.4"
+    "ts-jest": "^27.0.4",
+    "ts-node": "^9.1.1",
+    "tsconfig-paths": "^3.9.0",
+    "typescript": "^4.2.3"
   },
   "engines": {
     "node": "^12 || ^14",

+ 2 - 0
packages/app/.env.development

@@ -2,6 +2,8 @@
 ## Handled by Next.js with dotenv or dotenv-flow
 ## https://nextjs.org/docs/basic-features/environment-variables
 ##
+MIGRATIONS_DIR=src/migrations/
+
 FILE_UPLOAD=mongodb
 # MONGO_GRIDFS_TOTAL_LIMIT=10485760
 MATHJAX=1

+ 1 - 0
packages/app/.env.production

@@ -3,3 +3,4 @@
 ## https://nextjs.org/docs/basic-features/environment-variables
 ##
 FORMAT_NODE_LOG=false
+MIGRATIONS_DIR=dist/migrations/

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

@@ -25,11 +25,12 @@ module.exports = {
       name: 'axios',
       message: 'Please use src/utils/axios instead.',
     }],
+    '@typescript-eslint/no-var-requires': 'off',
+
     // 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-this-alias': ['warn'],
-    '@typescript-eslint/no-var-requires': ['warn'],
     'jest/no-done-callback': ['warn'],
   },
 };

+ 1 - 1
packages/app/.gitignore

@@ -5,8 +5,8 @@
 # dist
 /dist/
 /transpiled/
+/report/
 /public/static/js
-/public/static/dict
 /public/static/styles
 /public/uploads
 /tmp/

+ 1 - 12
packages/app/bin/cdn/cdn-resources-downloader.ts

@@ -4,9 +4,7 @@ import urljoin from 'url-join';
 import { Transform } from 'stream';
 import replaceStream from 'replacestream';
 
-import {
-  cdnLocalScriptRoot, cdnLocalDictRoot, cdnLocalStyleRoot, cdnLocalStyleWebRoot,
-} from '^/config/cdn';
+import { cdnLocalScriptRoot, cdnLocalStyleRoot, cdnLocalStyleWebRoot } from '^/config/cdn';
 import * as cdnManifests from '^/resource/cdn-manifests';
 
 import { CdnResource, CdnManifest } from '~/interfaces/cdn';
@@ -22,14 +20,6 @@ export default class CdnResourcesDownloader {
       return { manifest, outDir: cdnLocalScriptRoot };
     });
 
-    const cdnDictResources: CdnResource[] = cdnManifests.dict.map((manifest: CdnManifest) => {
-      return { manifest, outDir: cdnLocalDictRoot };
-    });
-
-    const dictExtensionOptions = {
-      ext: 'gz',
-    };
-
     const cdnStyleResources: CdnResource[] = cdnManifests.style.map((manifest) => {
       return { manifest, outDir: cdnLocalStyleRoot };
     });
@@ -42,7 +32,6 @@ export default class CdnResourcesDownloader {
 
     return Promise.all([
       this.downloadScripts(cdnScriptResources),
-      this.downloadScripts(cdnDictResources, dictExtensionOptions),
       this.downloadStyles(cdnStyleResources, dlStylesOptions),
     ]);
   }

+ 0 - 1
packages/app/config/cdn.js

@@ -4,6 +4,5 @@ import { projectRoot } from '~/utils/project-dir-utils';
 
 export const cdnLocalScriptRoot = path.join(projectRoot, 'public/static/js/cdn');
 export const cdnLocalScriptWebRoot = '/static/js/cdn';
-export const cdnLocalDictRoot = path.join(projectRoot, 'public/static/dict/cdn');
 export const cdnLocalStyleRoot = path.join(projectRoot, 'public/static/styles/cdn');
 export const cdnLocalStyleWebRoot = '/static/styles/cdn';

+ 2 - 2
packages/app/config/webpack.prod.js

@@ -15,7 +15,7 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
 /**
   * Webpack Constants
   */
-const { ANALYZE } = process.env;
+const { ANALYZE_BUNDLE_SIZE } = process.env;
 
 module.exports = require('./webpack.common')({
   mode: 'production',
@@ -60,7 +60,7 @@ module.exports = require('./webpack.common')({
     }),
 
     new BundleAnalyzerPlugin({
-      analyzerMode: ANALYZE ? 'static' : 'disabled',
+      analyzerMode: ANALYZE_BUNDLE_SIZE ? 'static' : 'disabled',
       reportFilename: path.resolve(__dirname, '../report/bundle-analyzer.html'),
       openAnalyzer: false,
     }),

+ 6 - 11
packages/app/docker/Dockerfile

@@ -72,15 +72,14 @@ RUN rm node_modules.tar
 ##
 FROM prebuilder-default AS prebuilder-nocdn
 
-# replace env.prod.js for NO_CDN
-COPY docker/nocdn/env.prod.js ${appDir}/config/
+# add dotenv file for NO_CDN
+COPY packages/app/docker/nocdn/.env.production.local ${appDir}/packages/app/
 
 
 
 ##
 ## builder
 ##
-# FROM prebuilder-${flavor}
 FROM prebuilder-${flavor} AS builder
 
 ENV appDir /opt/growi
@@ -113,7 +112,8 @@ RUN tar cf packages.tar \
   packages/app/public \
   packages/app/resource \
   packages/app/tmp \
-  packages/app/.env.production \
+  packages/app/migrate-mongo-config.js \
+  packages/app/.env.production* \
   packages/app/tsconfig.base.json \
   packages/app/tsconfig.json \
   packages/*/package.json \
@@ -140,11 +140,6 @@ RUN set -eux; \
 # verify that the binary works
 	gosu nobody true
 
-# Add Tini
-ENV TINI_VERSION v0.19.0
-ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
-RUN chmod +x /tini
-
 COPY --from=deps-resolver-prod --chown=node:node \
   ${appDir}/node_modules.tar ${appDir}/
 COPY --from=builder --chown=node:node \
@@ -168,5 +163,5 @@ WORKDIR ${appDir}/packages/app
 VOLUME /data
 EXPOSE 3000
 
-ENTRYPOINT ["/tini", "-e", "143", "--", "/docker-entrypoint.sh"]
-CMD ["node", "-r", "dotenv-flow/config", "--expose_gc", "dist/server/app.js"]
+ENTRYPOINT ["/docker-entrypoint.sh"]
+CMD ["yarn migrate && node -r dotenv-flow/config --expose_gc dist/server/app.js"]

+ 6 - 0
packages/app/docker/Dockerfile.dockerignore

@@ -0,0 +1,6 @@
+node_modules
+*/node_modules
+*/coverage
+*/dist
+*/Dockerfile
+*/*.dockerignore

+ 2 - 2
packages/app/docker/README.md

@@ -10,8 +10,8 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`4.4.5`, `4.4`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.5/docker/Dockerfile)
-* [`4.4.5-nocdn`, `4.4-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.5/docker/Dockerfile)
+* [`4.4.6`, `4.4`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.6/docker/Dockerfile)
+* [`4.4.6-nocdn`, `4.4-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.6/docker/Dockerfile)
 * [`4.3.3`, `4.3` (Dockerfile)](https://github.com/weseek/growi/blob/v4.3.3/docker/Dockerfile)
 * [`4.3.3-nocdn`, `4.3-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.3.3/docker/Dockerfile)
 

+ 1 - 1
packages/app/docker/docker-entrypoint.sh

@@ -11,4 +11,4 @@ fi
 chown -R node:node /data/uploads
 chown -h node:node ./public/uploads
 
-gosu node $@
+exec gosu node /bin/bash -c "$@"

+ 6 - 0
packages/app/docker/nocdn/.env.production.local

@@ -0,0 +1,6 @@
+
+##
+## Handled by Next.js with dotenv or dotenv-flow
+## https://nextjs.org/docs/basic-features/environment-variables
+##
+NO_CDN=true

+ 0 - 5
packages/app/docker/nocdn/env.prod.js

@@ -1,5 +0,0 @@
-module.exports = {
-  NODE_ENV: 'production',
-  NO_CDN: true,
-  // FORMAT_NODE_LOG: false,
-};

+ 21 - 5
packages/app/jest.config.js

@@ -14,10 +14,22 @@ module.exports = {
 
   preset: 'ts-jest/presets/js-with-ts',
 
-  globalSetup: '<rootDir>/src/test/global-setup.js',
-  globalTeardown: '<rootDir>/src/test/global-teardown.js',
-
   projects: [
+    {
+      displayName: 'unit',
+
+      preset: 'ts-jest/presets/js-with-ts',
+
+      rootDir: '.',
+      roots: ['<rootDir>/src'],
+      testMatch: ['<rootDir>/src/test/unit/**/*.test.ts', '<rootDir>/src/test/unit/**/*.test.js'],
+
+      testEnvironment: 'node',
+
+      // Automatically clear mock calls and instances between every test
+      clearMocks: true,
+      moduleNameMapper: MODULE_NAME_MAPPING,
+    },
     {
       displayName: 'server',
 
@@ -25,9 +37,13 @@ module.exports = {
 
       rootDir: '.',
       roots: ['<rootDir>/src'],
+      testMatch: ['<rootDir>/src/test/integration/**/*.test.ts', '<rootDir>/src/test/integration/**/*.test.js'],
+
       testEnvironment: 'node',
-      setupFilesAfterEnv: ['<rootDir>/src/test/setup.js'],
-      testMatch: ['<rootDir>/src/test/**/*.test.ts', '<rootDir>/src/test/**/*.test.js'],
+      globalSetup: '<rootDir>/src/test/integration/global-setup.js',
+      globalTeardown: '<rootDir>/src/test/integration/global-teardown.js',
+      setupFilesAfterEnv: ['<rootDir>/src/test/integration/setup.js'],
+
       // Automatically clear mock calls and instances between every test
       clearMocks: true,
       moduleNameMapper: MODULE_NAME_MAPPING,

+ 8 - 5
packages/app/config/migrate.js → packages/app/migrate-mongo-config.js

@@ -5,11 +5,15 @@
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
 
-import mongoose from 'mongoose';
+const { URL } = require('url');
 
-import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+// get migrationsDir from env var
+const migrationsDir = process.env.MIGRATIONS_DIR;
+if (migrationsDir == null) {
+  throw new Error('An env var MIGRATIONS_DIR must be set.');
+}
 
-const { URL } = require('url');
+const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('@growi/core');
 
 initMongooseGlobalSettings();
 
@@ -25,8 +29,7 @@ const mongodb = {
 };
 
 module.exports = {
-  mongoUri,
   mongodb,
-  migrationsDir: 'src/migrations/',
+  migrationsDir,
   changelogCollectionName: 'migrations',
 };

+ 16 - 13
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "4.4.6-RC.0",
+  "version": "4.4.7-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -14,13 +14,20 @@
     "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config --expose_gc dist/server/app.js",
     "server:ci": "yarn server --ci",
     "preserver": "yarn cross-env NODE_ENV=production yarn migrate",
+    "migrate": "node -r dotenv-flow/config node_modules/.bin/migrate-mongo up",
     "//// for development": "",
     "dev": "run-p dev:client dev:server",
     "dev:client": "yarn cross-env NODE_ENV=development webpack --config config/webpack.dev.js --progress --watch",
     "dev:client:nowatch": "yarn cross-env NODE_ENV=development webpack --config config/webpack.dev.js",
     "dev:server": "yarn cross-env NODE_ENV=development ts-node-dev --inspect --expose-gc -r tsconfig-paths/register -r dotenv-flow/config --transpile-only src/server/app.ts",
     "predev:client": "yarn cross-env NODE_ENV=development run-p resources:*",
-    "predev:server": "yarn cross-env NODE_ENV=development yarn migrate",
+    "predev:server": "yarn cross-env NODE_ENV=development yarn dev:migrate:up",
+    "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",
     "//// for CI": "",
     "dev:ci": "yarn dev:client:nowatch && yarn dev:server --ci",
     "predev:ci": "run-p resources:*",
@@ -39,11 +46,6 @@
     "openapi:v1": "yarn cross-env API_VERSION=1 yarn swagger-jsdoc -- \"src/server/*/*.js\" \"src/server/models/**/*.js\"",
     "resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
     "resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
-    "migrate": "yarn migrate:up",
-    "migrate:create": "yarn ts-node node_modules/.bin/migrate-mongo create -f config/migrate.js",
-    "migrate:status": "yarn ts-node node_modules/.bin/migrate-mongo status -f config/migrate.js",
-    "migrate:up": "yarn ts-node node_modules/.bin/migrate-mongo up -f config/migrate.js",
-    "migrate:down": "yarn ts-node node_modules/.bin/migrate-mongo down -f config/migrate.js",
     "ts-node": "ts-node -r tsconfig-paths/register -r dotenv-flow/config --transpile-only"
   },
   "// comments for dependencies": {
@@ -53,12 +55,13 @@
   },
   "dependencies": {
     "@browser-bunyan/console-formatted-stream": "^1.6.2",
+    "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^4.4.6-RC.0",
-    "@growi/plugin-attachment-refs": "^4.4.6-RC.0",
-    "@growi/plugin-pukiwiki-like-linker": "^4.4.6-RC.0",
-    "@growi/plugin-lsx": "^4.4.6-RC.0",
-    "@growi/slack": "^4.4.6-RC.0",
+    "@growi/codemirror-textlint": "^4.4.7-RC.0",
+    "@growi/plugin-attachment-refs": "^4.4.7-RC.0",
+    "@growi/plugin-pukiwiki-like-linker": "^4.4.7-RC.0",
+    "@growi/plugin-lsx": "^4.4.7-RC.0",
+    "@growi/slack": "^4.4.7-RC.0",
     "@promster/express": "^5.1.0",
     "@promster/server": "^6.0.3",
     "@slack/events-api": "^3.0.0",
@@ -154,7 +157,7 @@
     "@alienfast/i18next-loader": "^1.0.16",
     "@atlaskit/drawer": "^5.3.7",
     "@atlaskit/navigation-next": "^8.0.5",
-    "@growi/ui": "^4.4.6-RC.0",
+    "@growi/ui": "^4.4.7-RC.0",
     "@handsontable/react": "=2.1.0",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",

+ 3 - 0
packages/app/public/static/dict/base.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0803327762e1c93ca731e4319ab8343340f2806bb84941207782cde9d2d5a8eb
+size 3956825

+ 3 - 0
packages/app/public/static/dict/cc.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:02b7631be0d4de3a1a75cd9f9cc51536e4f94c9e6b389b813e06ba0f6e7de765
+size 1692067

+ 3 - 0
packages/app/public/static/dict/check.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:193ae0035fff6fe812b58d9ee730e7a7d7ee601d918481ce51075c58114f6cc9
+size 3111633

+ 3 - 0
packages/app/public/static/dict/tid.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d43d831cb6fb0f0a411739cd287a6d5e998e121a8daca614df14a81a0dcac586
+size 1605820

+ 3 - 0
packages/app/public/static/dict/tid_map.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:33efd5ffd87a70f669add093fa39dee44341d58f940844ef107c8fd98bb795b2
+size 1485576

+ 3 - 0
packages/app/public/static/dict/tid_pos.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:60dbfc99a6ab993f30c5dab648bec6ad7f9aaefa5c14e1843837d95e509f8895
+size 5916009

+ 3 - 0
packages/app/public/static/dict/unk.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f7f991cdeb9bfd3e9c0e4577cc50ee0815a11c508cccd444a9d3ab3c81521100
+size 10512

+ 3 - 0
packages/app/public/static/dict/unk_char.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9a8e86fd9aff32d323fbb59f5a7006f05927a11f8173c90712cc56293aeb3225
+size 306

+ 3 - 0
packages/app/public/static/dict/unk_compat.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:50f60aa29bc2e86c2903ab8c825bb6fa604d2b294d96941c1d3924259791899d
+size 338

+ 3 - 0
packages/app/public/static/dict/unk_invoke.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6b210889548457c3006913afd12c8b525562255f2709e404604be9614a25e94c
+size 1140

+ 3 - 0
packages/app/public/static/dict/unk_map.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6df12460e5477230bb6fd9641def918b699fc0a8868016b6c9f794488630509b
+size 1190

+ 3 - 0
packages/app/public/static/dict/unk_pos.dat.gz

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5b183a29f281acc7e0542beca47b83f7985047c0a2d27e78a66f32276be5ad11
+size 10540

+ 0 - 86
packages/app/resource/cdn-manifests.js

@@ -89,92 +89,6 @@ module.exports = {
       },
     },
   ],
-  dict: [
-    {
-      name: 'base.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/base.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'cc.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/cc.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'check.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/check.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'tid_map.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/tid_map.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'tid_pos.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/tid_pos.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'tid.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/tid.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'unk_char.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/unk_char.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'unk_compat.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/unk_compat.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'unk_invoke.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/unk_invoke.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'unk_map.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/unk_map.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'unk_pos.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/unk_pos.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'unk.dat',
-      url: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/unk.dat.gz',
-      args: {
-        integrity: '',
-      },
-    },
-  ],
   style: [
     {
       name: 'lato',

+ 1 - 1
packages/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx

@@ -65,7 +65,7 @@ class SamlSecurityManagementContents extends React.Component {
                 {t('security_setting.SAML.enable_saml')}
               </label>
             </div>
-            {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isSamlEnabled)
+            {(!adminGeneralSecurityContainer.state.setupStrategies.includes('saml') && isSamlEnabled)
               && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
           </div>
         </div>

+ 4 - 4
packages/app/src/components/Me/EditorSettings.tsx

@@ -46,10 +46,10 @@ const commonRulesMenuItems = [
     name: 'sentence-length',
     description: 'editor_settings.common_settings.sentence_length',
   },
-  {
-    name: 'en-capitalization',
-    description: 'editor_settings.common_settings.en_capitalization',
-  },
+  // {  // omit because en-pos package is too big
+  //   name: 'en-capitalization',
+  //   description: 'editor_settings.common_settings.en_capitalization',
+  // },
   {
     name: 'no-unmatched-pair',
     description: 'editor_settings.common_settings.no_unmatched_pair',

+ 2 - 5
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -33,8 +33,9 @@ import HandsontableModal from './HandsontableModal';
 import EditorIcon from './EditorIcon';
 import DrawioModal from './DrawioModal';
 
-
+// Textlint
 window.JSHINT = JSHINT;
+window.kuromojin = { dicPath: '/static/dict' };
 
 // set save handler
 codemirror.commands.save = (instance) => {
@@ -154,10 +155,6 @@ export default class CodeMirrorEditor extends AbstractEditor {
     this.cmCdnRoot = 'https://cdn.jsdelivr.net/npm/codemirror@5.42.0';
     this.cmNoCdnScriptRoot = '/static/js/cdn';
     this.cmNoCdnStyleRoot = '/static/styles/cdn';
-    window.kuromojin = this.props.noCdn
-      ? { dicPath: '/static/dict/cdn' }
-      : { dicPath: 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict' };
-
     this.interceptorManager = new InterceptorManager();
     this.interceptorManager.addInterceptors([
       new PreventMarkdownListInterceptor(),

+ 1 - 1
packages/app/src/components/Sidebar/RecentChanges.jsx

@@ -187,7 +187,7 @@ class RecentChanges extends React.Component {
               className="custom-control-input"
               type="checkbox"
               checked={this.state.isRecentChangesSidebarSmall}
-              onChange={e => this.setState({ isRecentChangesSidebarSmall: e.target.checked })}
+              onChange={this.changeSizeHandler}
             />
             <label className="custom-control-label" htmlFor="recentChangesResize">
             </label>

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

@@ -1,8 +1,7 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
-import { getModelSafely } from '~/server/util/mongoose-utils';
 
 const logger = loggerFactory('growi:migrate:make-email-unique');
 
@@ -10,7 +9,7 @@ module.exports = {
 
   async up(db, next) {
     logger.info('Start migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = getModelSafely('User') || require('~/server/models/user')();
 

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:init-serverurl');
@@ -20,7 +20,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     // find 'app:siteUrl'
     const siteUrlConfig = await Config.findOne({
@@ -77,7 +77,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     // remote 'app:siteUrl'
     await Config.findOneAndDelete({

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

@@ -1,8 +1,7 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
-import { getModelSafely } from '~/server/util/mongoose-utils';
 
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
 
@@ -29,7 +28,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const isPagegrouprelationsExists = await isCollectionExists(db, 'pagegrouprelations');
     if (!isPagegrouprelationsExists) {
@@ -73,7 +72,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = getModelSafely('Page') || require('~/server/models/page')();
     const UserGroup = getModelSafely('UserGroup') || require('~/server/models/user-group')();

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:abolish-crowi-classic-auth');
@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:abolish-crowi-classic-auth');
 module.exports = {
   async up(db, next) {
     logger.info('Start migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     // enable passport and delete configs for crowi classic auth
     await Promise.all([

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

@@ -1,9 +1,8 @@
 import mongoose from 'mongoose';
 
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
-import { getModelSafely } from '~/server/util/mongoose-utils';
 
 const logger = loggerFactory('growi:migrate:add-config-app-installed');
 
@@ -19,7 +18,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = getModelSafely('User') || require('~/server/models/user')();
 
@@ -49,7 +48,7 @@ module.exports = {
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     // remote 'app:siteUrl'
     await Config.findOneAndDelete({

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:adjust-page-grant');
@@ -9,7 +9,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = require('~/server/models/page')();
 

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
@@ -12,7 +12,7 @@ module.exports = {
 
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = require('~/server/models/page')();
 

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:make-root-page-public');
@@ -8,7 +8,7 @@ const logger = loggerFactory('growi:migrate:make-root-page-public');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = require('~/server/models/page')();
 

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:drop-configs-indices');
@@ -14,7 +14,7 @@ async function dropIndexIfExists(collection, indexName) {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const collection = db.collection('configs');
     await dropIndexIfExists(collection, 'ns_1');

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:drop-pages-indices');
@@ -21,7 +21,7 @@ async function dropIndexIfExists(db, collectionName, indexName) {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     await dropIndexIfExists(db, 'pages', 'lastUpdateUser_1');
     await dropIndexIfExists(db, 'pages', 'liker_1');

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
-import { pathUtils } from '@growi/core';
+import { pathUtils, getMongoUri, mongoOptions } from '@growi/core';
+
 
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:adjust-pages-path');
@@ -10,7 +10,7 @@ const logger = loggerFactory('growi:migrate:adjust-pages-path');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = require('~/server/models/page')();
 

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

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:drop-wrong-index-of-page-tag-relation');
@@ -21,7 +21,7 @@ async function dropIndexIfExists(db, collectionName, indexName) {
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     await dropIndexIfExists(db, 'pagetagrelations', 'page_1_user_1');
 

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

@@ -1,15 +1,14 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
-import { getModelSafely } from '~/server/util/mongoose-utils';
 
 const logger = loggerFactory('growi:migrate:remove-deleteduser-from-relationgroup');
 
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = getModelSafely('User') || require('~/server/models/user')();
     const UserGroupRelation = getModelSafely('UserGroupRelation') || require('~/server/models/user-group-relation')();

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const query = { key: 'customize:layout', value: JSON.stringify('crowi') };
 

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-behavior-type');
@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:remove-behavior-type');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.findOneAndDelete({ key: 'customize:behavior' }); // remove behavior
 
@@ -19,7 +19,7 @@ module.exports = {
   async down(db, client) {
     // do not rollback
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const insertConfig = new Config({
       ns: 'crowi',

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:update-theme-color-for-dark');
@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:update-theme-color-for-dark');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     await Promise.all([
       await Config.findOneAndUpdate({ key: 'customize:theme', value: JSON.stringify('default-dark') }, { value: JSON.stringify('default') }), // update default-dark

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

@@ -1,16 +1,15 @@
 import mongoose from 'mongoose';
 
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
-import { getModelSafely } from '~/server/util/mongoose-utils';
 
 const logger = loggerFactory('growi:migrate:normalize-locale-id');
 
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const User = getModelSafely('User') || require('~/server/models/user')();
 

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-layout-setting');
@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:remove-layout-setting');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const layoutType = await Config.findOne({ key: 'customize:layout' });
 
@@ -38,7 +38,7 @@ module.exports = {
 
   async down(db, client) {
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const theme = await Config.findOne({ key: 'customize:theme' });
     const insertLayoutType = (theme.value === '"kibela"') ? 'kibela' : 'growi';

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-layout-setting');
@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:remove-layout-setting');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const [accessKeyId, secretAccessKey] = await Promise.all([
       Config.findOne({ key: 'aws:accessKeyId' }),
@@ -55,7 +55,7 @@ module.exports = {
 
   async down(db, client) {
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.deleteMany({ key: { $in: ['mail:sesAccessKeyId', 'mail:sesSecretAccessKey'] } });
 

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

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:update-mail-transmission');
@@ -9,7 +9,7 @@ const logger = loggerFactory('growi:migrate:update-mail-transmission');
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const sesExist = await Config.findOne({
       ns: 'crowi',
@@ -33,7 +33,7 @@ module.exports = {
 
   async down(db, client) {
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     // remote 'mail:transmissionMethod'
     await Config.findOneAndDelete({

+ 2 - 2
packages/app/src/migrations/20200901034314-update-mail-transmission-fix.js

@@ -1,3 +1,4 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 import Config from '~/server/models/config';
@@ -5,12 +6,11 @@ import Config from '~/server/models/config';
 const logger = loggerFactory('growi:migrate:update-mail-transmission-fix');
 
 const mongoose = require('mongoose');
-const config = require('^/config/migrate');
 
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const transmissionMethod = await Config.findOne({
       ns: 'crowi',

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

@@ -1,3 +1,4 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 import Config from '~/server/models/config';
@@ -5,12 +6,11 @@ import Config from '~/server/models/config';
 const logger = loggerFactory('growi:migrate:remove-timeline-type');
 
 const mongoose = require('mongoose');
-const config = require('^/config/migrate');
 
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     await Config.findOneAndDelete({ key: 'customize:isEnabledTimeline' }); // remove timeline
 
@@ -20,7 +20,7 @@ module.exports = {
   async down(db, client) {
     // do not rollback
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const insertConfig = new Config({
       ns: 'crowi',

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

@@ -1,3 +1,4 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 import Config from '~/server/models/config';
@@ -5,7 +6,6 @@ import Config from '~/server/models/config';
 const logger = loggerFactory('growi:migrate:remove-timeline-type');
 
 const mongoose = require('mongoose');
-const config = require('^/config/migrate');
 
 const awsConfigs = [
   {
@@ -33,7 +33,7 @@ const awsConfigs = [
 module.exports = {
   async up(db, client) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const request = awsConfigs.map((awsConfig) => {
       return {
@@ -52,7 +52,7 @@ module.exports = {
   async down(db, client) {
     logger.info('Rollback migration');
 
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const request = awsConfigs.map((awsConfig) => {
       return {

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

@@ -1,7 +1,6 @@
 import mongoose from 'mongoose';
 
-import config from '^/config/migrate';
-import { getModelSafely } from '~/server/util/mongoose-utils';
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
@@ -9,7 +8,7 @@ const logger = loggerFactory('growi:migrate:remove-crowi-lauout');
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     const Page = getModelSafely('Page') || require('~/server/models/page')();
 

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

@@ -1,64 +1,55 @@
 import mongoose from 'mongoose';
 
+import { getMongoUri, mongoOptions } from '@growi/core';
 import Config from '~/server/models/config';
-import config from '^/config/migrate';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:update-configs-for-slackbot');
 
+// key: oldKey, value: newKey
+const keyMap = {
+  'slackbot:proxyServerUri': 'slackbot:proxyUri',
+  'slackbot:token': 'slackbot:withoutProxy:botToken',
+  'slackbot:signingSecret': 'slackbot:withoutProxy:signingSecret',
+};
+
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
-    await Config.bulkWrite([
-      {
-        updateOne: {
-          filter: { key: 'slackbot:proxyServerUri' },
-          update: { key: 'slackbot:proxyUri' },
-        },
-      },
-      {
-        updateOne: {
-          filter: { key: 'slackbot:token' },
-          update: { key: 'slackbot:withoutProxy:botToken' },
-        },
-      },
-      {
-        updateOne: {
-          filter: { key: 'slackbot:signingSecret' },
-          update: { key: 'slackbot:withoutProxy:signingSecret' },
-        },
-      },
-    ]);
+    for await (const [oldKey, newKey] of Object.entries(keyMap)) {
+      const isExist = (await Config.count({ key: newKey })) > 0;
+
+      // remove old key
+      if (isExist) {
+        await Config.findOneAndRemove({ key: oldKey });
+      }
+      // update with new key
+      else {
+        await Config.findOneAndUpdate({ key: oldKey }, { key: newKey });
+      }
+    }
 
     logger.info('Migration has successfully applied');
   },
 
   async down(db) {
     logger.info('Rollback migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
-
-    await Config.bulkWrite([
-      {
-        updateOne: {
-          filter: { key: 'slackbot:proxyUri' },
-          update: { key: 'slackbot:proxyServerUri' },
-        },
-      },
-      {
-        updateOne: {
-          filter: { key: 'slackbot:withoutProxy:botToken' },
-          update: { key: 'slackbot:token' },
-        },
-      },
-      {
-        updateOne: {
-          filter: { key: 'slackbot:withoutProxy:signingSecret' },
-          update: { key: 'slackbot:signingSecret' },
-        },
-      },
-    ]);
+    mongoose.connect(getMongoUri(), mongoOptions);
+
+    for await (const [oldKey, newKey] of Object.entries(keyMap)) {
+      const isExist = (await Config.count({ key: oldKey })) > 0;
+
+      // remove new key
+      if (isExist) {
+        await Config.findOneAndRemove({ key: newKey });
+      }
+      // update with old key
+      else {
+        await Config.findOneAndUpdate({ key: newKey }, { key: oldKey });
+      }
+    }
 
     logger.info('Migration has successfully applied');
   },

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

@@ -1,8 +1,7 @@
 import mongoose from 'mongoose';
 
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
-import { getModelSafely } from '~/server/util/mongoose-utils';
-import config from '^/config/migrate';
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:slack-app-integration-set-default-value');
@@ -10,7 +9,7 @@ const logger = loggerFactory('growi:migrate:slack-app-integration-set-default-va
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    mongoose.connect(getMongoUri(), mongoOptions);
 
     // Add columns + set all default commands if supportedCommandsForBroadcastUse column does not exist
     const SlackAppIntegration = getModelSafely('SlackAppIntegration') || require('~/server/models/slack-app-integration')();
@@ -18,7 +17,7 @@ module.exports = {
     // Add togetter command if supportedCommandsForBroadcastUse already exists
     const slackAppIntegrations = await SlackAppIntegration.find();
     slackAppIntegrations.forEach(async(doc) => {
-      if (!doc.supportedCommandsForSingleUse.includes('togetter')) {
+      if (doc.supportedCommandsForSingleUse != null && !doc.supportedCommandsForSingleUse.includes('togetter')) {
         doc.supportedCommandsForSingleUse.push('togetter');
       }
       await doc.save();

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

@@ -1,51 +1,66 @@
 import mongoose from 'mongoose';
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
 
-import config from '^/config/migrate';
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import loggerFactory from '~/utils/logger';
-import { getModelSafely } from '~/server/util/mongoose-utils';
 
 
 const logger = loggerFactory('growi:migrate:update-configs-for-slackbot');
 
+// create default data
+const defaultDataForBroadcastUse = {};
+defaultSupportedCommandsNameForBroadcastUse.forEach((commandName) => {
+  defaultDataForBroadcastUse[commandName] = false;
+});
+const defaultDataForSingleUse = {};
+defaultSupportedCommandsNameForSingleUse.forEach((commandName) => {
+  defaultDataForSingleUse[commandName] = false;
+});
+
 module.exports = {
   async up(db) {
     logger.info('Apply migration');
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const SlackAppIntegration = getModelSafely('SlackAppIntegration') || require('~/server/models/slack-app-integration')();
 
     const slackAppIntegrations = await SlackAppIntegration.find();
 
-    // create default data
-    const defaultDataForBroadcastUse = {};
-    defaultSupportedCommandsNameForBroadcastUse.forEach((commandName) => {
-      defaultDataForBroadcastUse[commandName] = false;
-    });
-    const defaultDataForSingleUse = {};
-    defaultSupportedCommandsNameForSingleUse.forEach((commandName) => {
-      defaultDataForSingleUse[commandName] = false;
-    });
+    if (slackAppIntegrations.length === 0) return;
 
     // create operations
     const operations = slackAppIntegrations.map((doc) => {
-      const copyForBroadcastUse = { ...defaultDataForBroadcastUse };
-      const copyForSingleUse = { ...defaultDataForSingleUse };
-      // when the document does NOT have supportedCommandsFor... columns
-      if (doc._doc.supportedCommandsForBroadcastUse == null) {
-        defaultSupportedCommandsNameForBroadcastUse.forEach((commandName) => {
+      let copyForBroadcastUse = { ...defaultDataForBroadcastUse };
+      let copyForSingleUse = { ...defaultDataForSingleUse };
+      // when the document already has permissionsFor... colums
+      if (doc._doc.permissionsForBroadcastUseCommands != null) {
+        // merge
+        copyForBroadcastUse = {
+          ...defaultDataForBroadcastUse,
+          ...Object.fromEntries(doc._doc.permissionsForBroadcastUseCommands),
+        };
+        copyForSingleUse = {
+          ...defaultDataForSingleUse,
+          ...Object.fromEntries(doc._doc.permissionsForSingleUseCommands),
+        };
+      }
+      // when the document has supportedCommandsFor... columns
+      else if (doc._doc.supportedCommandsForBroadcastUse != null) {
+        // merge
+        doc._doc.supportedCommandsForBroadcastUse.forEach((commandName) => {
           copyForBroadcastUse[commandName] = true;
         });
-        defaultSupportedCommandsNameForSingleUse.forEach((commandName) => {
+        doc._doc.supportedCommandsForSingleUse.forEach((commandName) => {
           copyForSingleUse[commandName] = true;
         });
       }
-      // // when the document has supportedCommandsFor... columns
+      // when the document does NOT have supportedCommandsFor... columns
       else {
-        doc._doc.supportedCommandsForBroadcastUse.forEach((commandName) => {
+        // turn on all
+        defaultSupportedCommandsNameForBroadcastUse.forEach((commandName) => {
           copyForBroadcastUse[commandName] = true;
         });
-        doc._doc.supportedCommandsForSingleUse.forEach((commandName) => {
+        defaultSupportedCommandsNameForSingleUse.forEach((commandName) => {
           copyForSingleUse[commandName] = true;
         });
       }
@@ -53,35 +68,35 @@ module.exports = {
       return {
         updateOne: {
           filter: { _id: doc._id },
-          update: [
-            {
-              $set: {
-                permissionsForBroadcastUseCommands: copyForBroadcastUse,
-                permissionsForSingleUseCommands: copyForSingleUse,
-              },
+          update: {
+            $set: {
+              permissionsForBroadcastUseCommands: copyForBroadcastUse,
+              permissionsForSingleUseCommands: copyForSingleUse,
             },
-            {
-              $unset: ['supportedCommandsForBroadcastUse', 'supportedCommandsForSingleUse'],
+            $unset: {
+              supportedCommandsForBroadcastUse: '',
+              supportedCommandsForSingleUse: '',
             },
-          ],
+          },
         },
       };
     });
 
-    await SlackAppIntegration.bulkWrite(operations);
+    await db.collection('slackappintegrations').bulkWrite(operations);
 
     logger.info('Migration has successfully applied');
   },
 
   async down(db, next) {
     logger.info('Rollback migration');
-    // return next();
-    mongoose.connect(config.mongoUri, config.mongodb.options);
+    await mongoose.connect(getMongoUri(), mongoOptions);
 
     const SlackAppIntegration = getModelSafely('SlackAppIntegration') || require('~/server/models/slack-app-integration')();
 
     const slackAppIntegrations = await SlackAppIntegration.find();
 
+    if (slackAppIntegrations.length === 0) return next();
+
     // create operations
     const operations = slackAppIntegrations.map((doc) => {
       const dataForBroadcastUse = [];
@@ -100,22 +115,21 @@ module.exports = {
       return {
         updateOne: {
           filter: { _id: doc._id },
-          update: [
-            {
-              $set: {
-                supportedCommandsForBroadcastUse: dataForBroadcastUse,
-                supportedCommandsForSingleUse: dataForSingleUse,
-              },
+          update: {
+            $set: {
+              supportedCommandsForBroadcastUse: dataForBroadcastUse,
+              supportedCommandsForSingleUse: dataForSingleUse,
             },
-            {
-              $unset: ['permissionsForBroadcastUseCommands', 'permissionsForSingleUseCommands'],
+            $unset: {
+              permissionsForBroadcastUseCommands: '',
+              permissionsForSingleUseCommands: '',
             },
-          ],
+          },
         },
       };
     });
 
-    await SlackAppIntegration.bulkWrite(operations);
+    await db.collection('slackappintegrations').bulkWrite(operations);
 
     next();
     logger.info('Migration has successfully applied');

+ 1 - 1
packages/app/src/server/console.js

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

+ 28 - 5
packages/app/src/server/crowi/index.js

@@ -1,15 +1,18 @@
 /* eslint-disable @typescript-eslint/no-this-alias */
 
 import path from 'path';
+import http from 'http';
 import mongoose from 'mongoose';
 
+import { createTerminus } from '@godaddy/terminus';
+
+import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '@growi/core';
 import pkg from '^/package.json';
 
 import CdnResourcesService from '~/services/cdn-resources-service';
 import InterceptorManager from '~/services/interceptor-manager';
 import Xss from '~/services/xss';
 import loggerFactory from '~/utils/logger';
-import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import { projectRoot } from '~/utils/project-dir-utils';
 
 import ConfigManager from '../service/config-manager';
@@ -409,10 +412,17 @@ Crowi.prototype.start = async function() {
   this.pluginService = new PluginService(this, express);
   await this.pluginService.autoDetectAndLoadPlugins();
 
-  const server = (this.node_env === 'development') ? this.crowiDev.setupServer(express) : express;
+  const app = (this.node_env === 'development') ? this.crowiDev.setupServer(express) : express;
+
+  const httpServer = http.createServer(app);
+
+  // setup terminus
+  this.setupTerminus(httpServer);
+  // attach to socket.io
+  this.socketIoService.attachServer(httpServer);
 
   // listen
-  const serverListening = server.listen(this.port, () => {
+  const serverListening = httpServer.listen(this.port, () => {
     logger.info(`[${this.node_env}] Express server is listening on port ${this.port}`);
     if (this.node_env === 'development') {
       this.crowiDev.setupExpressAfterListening(express);
@@ -428,8 +438,6 @@ Crowi.prototype.start = async function() {
     });
   }
 
-  this.socketIoService.attachServer(serverListening);
-
   // setup Express Routes
   this.setupRoutesAtLast();
 
@@ -463,6 +471,21 @@ Crowi.prototype.buildServer = async function() {
   this.express = express;
 };
 
+Crowi.prototype.setupTerminus = function(server) {
+  createTerminus(server, {
+    signals: ['SIGINT', 'SIGTERM'],
+    onSignal: async() => {
+      logger.info('Server is starting cleanup');
+
+      await mongoose.disconnect();
+      return;
+    },
+    onShutdown: async() => {
+      logger.info('Cleanup finished, server is shutting down');
+    },
+  });
+};
+
 /**
  * setup Express Routes
  * !! this must be at last because it includes '/*' route !!

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

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

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

@@ -1,7 +1,7 @@
 import {
   Schema, Model, Document,
 } from 'mongoose';
-import { getOrCreateModel } from '../util/mongoose-utils';
+import { getOrCreateModel } from '@growi/core';
 
 
 export interface ILintRule {

+ 1 - 1
packages/app/src/server/models/password-reset-order.ts

@@ -4,7 +4,7 @@ import mongoose, {
 
 import uniqueValidator from 'mongoose-unique-validator';
 import crypto from 'crypto';
-import { getOrCreateModel } from '../util/mongoose-utils';
+import { getOrCreateModel } from '@growi/core';
 
 const ObjectId = mongoose.Schema.Types.ObjectId;
 

+ 1 - 1
packages/app/src/server/models/update-post.ts

@@ -3,7 +3,7 @@
 import {
   Types, Schema, Model, Document,
 } from 'mongoose';
-import { getOrCreateModel } from '../util/mongoose-utils';
+import { getOrCreateModel } from '@growi/core';
 
 export interface IUpdatePost {
   pathPattern: string

+ 2 - 3
packages/app/src/server/routes/apiv3/slack-integration-settings.js

@@ -164,7 +164,7 @@ module.exports = (crowi) => {
    */
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
 
-    const { configManager } = crowi;
+    const { configManager, slackIntegrationService } = crowi;
     const currentBotType = configManager.getConfig('crowi', 'slackbot:currentBotType');
 
     // retrieve settings
@@ -177,8 +177,7 @@ module.exports = (crowi) => {
       settings.commandPermission = JSON.parse(configManager.getConfig('crowi', 'slackbot:withoutProxy:commandPermission'));
     }
     else {
-      settings.proxyServerUri = crowi.configManager.getConfig('crowi', 'slackbot:proxyUri');
-      settings.proxyUriEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:proxyUri');
+      settings.proxyServerUri = slackIntegrationService.proxyUriForCurrentType;
     }
 
     // retrieve connection statuses

+ 5 - 2
packages/app/src/server/service/socket-io.js

@@ -1,7 +1,8 @@
+import { Server } from 'socket.io';
+
 import loggerFactory from '~/utils/logger';
 import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
 
-const socketIo = require('socket.io');
 const expressSession = require('express-session');
 const passport = require('passport');
 
@@ -26,9 +27,11 @@ class SocketIoService {
 
   // Since the Order is important, attachServer() should be async
   async attachServer(server) {
-    this.io = socketIo(server, {
+    this.io = new Server({
       transports: ['websocket'],
+      serveClient: false,
     });
+    this.io.attach(server);
 
     // create namespace for admin
     this.adminNamespace = this.io.of('/admin');

+ 0 - 40
packages/app/src/test/config/migrate.test.js

@@ -1,40 +0,0 @@
-describe('config/migrate.js', () => {
-
-  beforeEach(async() => {
-    jest.resetModules();
-  });
-
-  /* eslint-disable indent */
-  describe.each`
-    MONGO_URI                                         | expectedUrl                                       | expectedDbName
-    ${'mongodb://example.com/growi'}                  | ${'mongodb://example.com/growi'}                  | ${'growi'}
-    ${'mongodb://user:pass@example.com/growi'}        | ${'mongodb://user:pass@example.com/growi'}        | ${'growi'}
-    ${'mongodb://example.com/growi?replicaSet=mySet'} | ${'mongodb://example.com/growi?replicaSet=mySet'} | ${'growi'}
-  `('returns', ({ MONGO_URI, expectedUrl, expectedDbName }) => {
-    test(`when 'MONGO_URI' is '${MONGO_URI}`, () => {
-
-      const initMongooseGlobalSettingsMock = jest.fn();
-
-      // mock for mongoose-utils
-      jest.doMock('~/server/util/mongoose-utils', () => {
-        return {
-          initMongooseGlobalSettings: initMongooseGlobalSettingsMock,
-          getMongoUri: () => {
-            return MONGO_URI;
-          },
-        };
-      });
-
-      const { mongoUri, mongodb } = require('^/config/migrate');
-
-      jest.dontMock('~/server/util/mongoose-utils');
-
-      expect(initMongooseGlobalSettingsMock).toHaveBeenCalledTimes(1);
-      expect(mongoUri).toBe(MONGO_URI);
-      expect(mongodb.url).toBe(expectedUrl);
-      expect(mongodb.databaseName).toBe(expectedDbName);
-    });
-  });
-  /* eslint-enable indent */
-
-});

+ 0 - 0
packages/app/src/test/crowi/crowi.test.js → packages/app/src/test/integration/crowi/crowi.test.js


+ 1 - 1
packages/app/src/test/global-setup.js → packages/app/src/test/integration/global-setup.js

@@ -9,7 +9,7 @@ import 'tsconfig-paths/register';
 
 import mongoose from 'mongoose';
 
-import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '@growi/core';
 
 // check env
 if (process.env.NODE_ENV !== 'test') {

+ 0 - 0
packages/app/src/test/global-teardown.js → packages/app/src/test/integration/global-teardown.js


+ 0 - 0
packages/app/src/test/middlewares/access-token-parser.test.js → packages/app/src/test/integration/middlewares/access-token-parser.test.js


+ 0 - 0
packages/app/src/test/middlewares/login-required.test.js → packages/app/src/test/integration/middlewares/login-required.test.js


+ 122 - 0
packages/app/src/test/integration/migrations/20210913153942-migrate-slack-app-integration-schema.test.ts

@@ -0,0 +1,122 @@
+import mongoose from 'mongoose';
+import { Collection } from 'mongodb';
+import { getMongoUri, mongoOptions } from '@growi/core';
+
+const migrate = require('../../../migrations/20210913153942-migrate-slack-app-integration-schema');
+
+describe('migrate-slack-app-integration-schema', () => {
+
+  let collection: Collection;
+
+  beforeAll(async() => {
+    await mongoose.connect(getMongoUri(), mongoOptions);
+    collection = mongoose.connection.db.collection('slackappintegrations');
+
+    await collection.insertMany([
+      {
+        tokenGtoP: 'tokenGtoP1', tokenPtoG: 'tokenPtoG1', permissionsForBroadcastUseCommands: { foo: true }, permissionsForSingleUseCommands: { bar: true },
+      },
+      {
+        tokenGtoP: 'tokenGtoP2', tokenPtoG: 'tokenPtoG2', supportedCommandsForBroadcastUse: ['foo'], supportedCommandsForSingleUse: ['bar'],
+      },
+      {
+        tokenGtoP: 'tokenGtoP3', tokenPtoG: 'tokenPtoG3',
+      },
+    ]);
+  });
+
+  test('up is applied successfully', async() => {
+    // setup
+    const doc1 = await collection.findOne({ tokenGtoP: 'tokenGtoP1' });
+    const doc2 = await collection.findOne({ tokenGtoP: 'tokenGtoP2' });
+    const doc3 = await collection.findOne({ tokenGtoP: 'tokenGtoP3' });
+    expect(doc1 != null).toBeTruthy();
+    expect(doc2 != null).toBeTruthy();
+    expect(doc3 != null).toBeTruthy();
+    expect(doc1).toStrictEqual({
+      _id: doc1._id,
+      tokenGtoP: 'tokenGtoP1',
+      tokenPtoG: 'tokenPtoG1',
+      permissionsForBroadcastUseCommands: {
+        foo: true,
+      },
+      permissionsForSingleUseCommands: {
+        bar: true,
+      },
+    });
+    expect(doc2).toStrictEqual({
+      _id: doc2._id,
+      tokenGtoP: 'tokenGtoP2',
+      tokenPtoG: 'tokenPtoG2',
+      supportedCommandsForBroadcastUse: [
+        'foo',
+      ],
+      supportedCommandsForSingleUse: [
+        'bar',
+      ],
+    });
+    expect(doc3).toStrictEqual({
+      _id: doc3._id,
+      tokenGtoP: 'tokenGtoP3',
+      tokenPtoG: 'tokenPtoG3',
+    });
+
+    // when
+    await migrate.up(mongoose.connection.db);
+
+    // then
+    const fixedDoc1 = await collection.findOne({ tokenGtoP: 'tokenGtoP1' });
+    const fixedDoc2 = await collection.findOne({ tokenGtoP: 'tokenGtoP2' });
+    const fixedDoc3 = await collection.findOne({ tokenGtoP: 'tokenGtoP3' });
+    expect(fixedDoc1 != null).toBeTruthy();
+    expect(fixedDoc2 != null).toBeTruthy();
+    expect(fixedDoc3 != null).toBeTruthy();
+    expect(fixedDoc1.supportedCommandsForBroadcastUse).toBeUndefined();
+    expect(fixedDoc1.supportedCommandsForSingleUse).toBeUndefined();
+    expect(fixedDoc2.supportedCommandsForBroadcastUse).toBeUndefined();
+    expect(fixedDoc2.supportedCommandsForSingleUse).toBeUndefined();
+    expect(fixedDoc3.supportedCommandsForBroadcastUse).toBeUndefined();
+    expect(fixedDoc3.supportedCommandsForSingleUse).toBeUndefined();
+    expect(fixedDoc1).toStrictEqual({
+      _id: doc1._id,
+      tokenGtoP: 'tokenGtoP1',
+      tokenPtoG: 'tokenPtoG1',
+      permissionsForBroadcastUseCommands: {
+        foo: true,
+        search: false,
+      },
+      permissionsForSingleUseCommands: {
+        bar: true,
+        create: false,
+        togetter: false,
+      },
+    });
+    expect(fixedDoc2).toStrictEqual({
+      _id: doc2._id,
+      tokenGtoP: 'tokenGtoP2',
+      tokenPtoG: 'tokenPtoG2',
+      permissionsForBroadcastUseCommands: {
+        foo: true,
+        search: false,
+      },
+      permissionsForSingleUseCommands: {
+        bar: true,
+        create: false,
+        togetter: false,
+      },
+    });
+    expect(fixedDoc3).toStrictEqual({
+      _id: doc3._id,
+      tokenGtoP: 'tokenGtoP3',
+      tokenPtoG: 'tokenPtoG3',
+      permissionsForBroadcastUseCommands: {
+        search: true,
+      },
+      permissionsForSingleUseCommands: {
+        create: true,
+        togetter: true,
+      },
+    });
+  });
+
+});

+ 0 - 0
packages/app/src/test/models/config.test.js → packages/app/src/test/integration/models/config.test.js


+ 0 - 0
packages/app/src/test/models/page.test.js → packages/app/src/test/integration/models/page.test.js


+ 0 - 0
packages/app/src/test/models/share-link.test.js → packages/app/src/test/integration/models/share-link.test.js


+ 0 - 0
packages/app/src/test/models/update-post.test.js → packages/app/src/test/integration/models/update-post.test.js


+ 0 - 0
packages/app/src/test/models/user.test.js → packages/app/src/test/integration/models/user.test.js


+ 0 - 0
packages/app/src/test/service/acl.test.js → packages/app/src/test/integration/service/acl.test.js


+ 0 - 0
packages/app/src/test/service/config-manager.test.js → packages/app/src/test/integration/service/config-manager.test.js


+ 2 - 2
packages/app/src/test/service/page.test.js → packages/app/src/test/integration/service/page.test.js

@@ -505,8 +505,8 @@ describe('PageService', () => {
   describe('duplicate page', () => {
     let duplicateDescendantsWithStreamSpy;
 
-    jest.mock('../../server/models/serializers/page-serializer');
-    const { serializePageSecurely } = require('../../server/models/serializers/page-serializer');
+    jest.mock('~/server/models/serializers/page-serializer');
+    const { serializePageSecurely } = require('~/server/models/serializers/page-serializer');
     serializePageSecurely.mockImplementation(page => page);
 
     beforeEach(async() => {

+ 0 - 0
packages/app/src/test/service/passport.test.js → packages/app/src/test/integration/service/passport.test.js


+ 0 - 0
packages/app/src/test/service/search-delegator/searchbox.test.js → packages/app/src/test/integration/service/search-delegator/searchbox.test.js


+ 1 - 5
packages/app/src/test/setup-crowi.js → packages/app/src/test/integration/setup-crowi.js

@@ -2,7 +2,7 @@ import Crowi from '~/server/crowi';
 
 let _instance = null;
 
-async function getInstance(isNewInstance) {
+export async function getInstance(isNewInstance) {
   if (isNewInstance) {
     const crowi = new Crowi();
     await crowi.initForTest();
@@ -16,7 +16,3 @@ async function getInstance(isNewInstance) {
   }
   return _instance;
 }
-
-module.exports = {
-  getInstance,
-};

+ 1 - 1
packages/app/src/test/setup.js → packages/app/src/test/integration/setup.js

@@ -7,7 +7,7 @@
 
 const mongoose = require('mongoose');
 
-const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
+const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('@growi/core');
 
 mongoose.Promise = global.Promise;
 

+ 0 - 0
packages/app/src/test/utils/slack-legacy.test.js → packages/app/src/test/integration/utils/slack-legacy.test.js


+ 0 - 0
packages/app/src/test/middlewares/safe-redirect.test.js → packages/app/src/test/unit/middlewares/safe-redirect.test.js


+ 71 - 0
packages/app/src/test/unit/migrate-mongo-config.test.js

@@ -0,0 +1,71 @@
+describe('config/migrate.js', () => {
+
+  beforeEach(async() => {
+    jest.resetModules();
+  });
+
+  test('throws an error when MIGRATIONS_DIR is not set', () => {
+
+    const initMongooseGlobalSettingsMock = jest.fn();
+
+    // mock for mongoose-utils
+    jest.doMock('@growi/core', () => {
+      return {
+        initMongooseGlobalSettings: initMongooseGlobalSettingsMock,
+      };
+    });
+
+    const requireConfig = () => {
+      require('^/migrate-mongo-config');
+    };
+
+    expect(requireConfig).toThrow('An env var MIGRATIONS_DIR must be set.');
+
+    jest.dontMock('@growi/core');
+
+    expect(initMongooseGlobalSettingsMock).not.toHaveBeenCalled();
+  });
+
+  /* eslint-disable indent */
+  describe.each`
+    MONGO_URI                                         | expectedDbName
+    ${'mongodb://example.com/growi'}                  | ${'growi'}
+    ${'mongodb://user:pass@example.com/growi'}        | ${'growi'}
+    ${'mongodb://example.com/growi?replicaSet=mySet'} | ${'growi'}
+  `('returns', ({ MONGO_URI, expectedDbName }) => {
+
+    beforeEach(async() => {
+      process.env.MIGRATIONS_DIR = 'testdir/migrations';
+    });
+
+    test(`when 'MONGO_URI' is '${MONGO_URI}`, () => {
+
+      const initMongooseGlobalSettingsMock = jest.fn();
+      const mongoOptionsMock = jest.fn();
+
+      // mock for mongoose-utils
+      jest.doMock('@growi/core', () => {
+        return {
+          initMongooseGlobalSettings: initMongooseGlobalSettingsMock,
+          getMongoUri: () => {
+            return MONGO_URI;
+          },
+          mongoOptions: mongoOptionsMock,
+        };
+      });
+
+      const { mongodb, migrationsDir, changelogCollectionName } = require('^/migrate-mongo-config');
+
+      jest.dontMock('@growi/core');
+
+      expect(initMongooseGlobalSettingsMock).toHaveBeenCalledTimes(1);
+      expect(mongodb.url).toBe(MONGO_URI);
+      expect(mongodb.databaseName).toBe(expectedDbName);
+      expect(mongodb.options).toBe(mongoOptionsMock);
+      expect(migrationsDir).toBe('testdir/migrations');
+      expect(changelogCollectionName).toBe('migrations');
+    });
+  });
+  /* eslint-enable indent */
+
+});

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