Procházet zdrojové kódy

Merge pull request #9447 from weseek/master

Release v7.1.5
Yuki Takei před 1 rokem
rodič
revize
56c0ac679b

+ 1 - 0
apps/app/bin/swagger-jsdoc/definition-apiv3.js

@@ -36,6 +36,7 @@ module.exports = {
       tags: [
         'Attachment',
         'Bookmarks',
+        'BookmarkFolders',
         'Page',
         'Pages',
         'Revisions',

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.1.4",
+  "version": "7.1.5-RC.0",
   "license": "MIT",
   "private": "true",
   "scripts": {

+ 7 - 1
apps/app/src/client/components/Admin/ImportData/GrowiArchive/UploadForm.jsx

@@ -75,7 +75,12 @@ class UploadForm extends React.Component {
             </div>
           </div>
           <div className="row">
-            <div className="mx-auto mt-3">
+            <div className="mt-4 text-center">
+              { this.props.onDiscard && (
+                <button type="button" className="btn btn-outline-secondary mx-1" onClick={this.props.onDiscard}>
+                  {t('admin:importer_management.growi_settings.discard')}
+                </button>
+              ) }
               <button type="submit" className="btn btn-primary" disabled={!this.validateForm()}>
                 {t('admin:importer_management.growi_settings.upload')}
               </button>
@@ -91,6 +96,7 @@ class UploadForm extends React.Component {
 UploadForm.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   onUpload: PropTypes.func.isRequired,
+  onDiscard: PropTypes.func,
   isTheSameVersion: PropTypes.bool,
   onVersionMismatch: PropTypes.func,
 };

+ 1 - 0
apps/app/src/client/components/Admin/ImportData/GrowiArchiveSection.jsx

@@ -121,6 +121,7 @@ class GrowiArchiveSection extends React.Component {
           : (
             <UploadForm
               onUpload={this.handleUpload}
+              onDiscard={this.state.fileName != null ? this.discardData : undefined}
               onVersionMismatch={this.handleMismatchedVersions}
             />
           )}

+ 1 - 2
apps/app/src/client/components/Common/CopyDropdown/CopyDropdown.jsx

@@ -195,8 +195,7 @@ export const CopyDropdown = (props) => {
                 <DropdownItemContents
                   title={t('copy_to_clipboard.Page path and permanent link')}
                   contents={<>{pagePathWithParams}<br />{permalink}</>}
-                  className="text-truncate"
-                  style={{ direction: 'rtl' }}
+                  className="text-truncate d-block"
                 />
               </DropdownItem>
             </CopyToClipboard>

+ 12 - 0
apps/app/src/client/components/Sidebar/Sidebar.module.scss

@@ -44,6 +44,9 @@
         min-height: 50vh;
         max-height: calc(100vh - var.$grw-sidebar-nav-width * 2);
         border-radius: 0 4px 4px 0 ;
+        .simple-scrollbar {
+          max-height: inherit;
+        }
       }
     }
   }
@@ -74,6 +77,15 @@
 }
 
 
+.grw-sidebar :global {
+
+  // overwrite simplebar-react css
+  .simplebar-scrollbar::before {
+    background-color:var(--bs-gray-500);
+  }
+
+}
+
 @include bs.color-mode(light) {
   .grw-sidebar :global {
     --bs-border-color: var(--grw-highlight-200);

+ 11 - 4
apps/app/src/client/components/Sidebar/Sidebar.tsx

@@ -6,6 +6,7 @@ import {
 
 import withLoadingProps from 'next-dynamic-loading-props';
 import dynamic from 'next/dynamic';
+import SimpleBar from 'simplebar-react';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 import { SidebarMode } from '~/interfaces/ui';
@@ -27,6 +28,7 @@ import type { ResizableAreaProps } from './ResizableArea/props';
 import { SidebarHead } from './SidebarHead';
 import { SidebarNav, type SidebarNavProps } from './SidebarNav';
 
+import 'simplebar-react/dist/simplebar.min.css';
 import styles from './Sidebar.module.scss';
 
 
@@ -173,11 +175,16 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
     <div className={`flex-expand-horiz ${className}`} onMouseLeave={mouseLeaveHandler}>
       <Nav onPrimaryItemHover={primaryItemHoverHandler} />
       <div
-        ref={sidebarScrollerRef}
-        className={`sidebar-contents-container flex-grow-1 overflow-y-auto overflow-x-hidden ${closedClass} ${openedClass}`}
-        style={{ width: collapsibleContentsWidth }}
+        className={`sidebar-contents-container flex-grow-1 overflow-hidden ${closedClass} ${openedClass}`}
       >
-        {children}
+        <SimpleBar
+          scrollableNodeProps={{ ref: sidebarScrollerRef }}
+          className="simple-scrollbar h-100"
+          style={{ width: collapsibleContentsWidth }}
+          autoHide
+        >
+          {children}
+        </SimpleBar>
       </div>
     </div>
   );

+ 1 - 0
apps/app/src/pages/[[...path]].page.tsx

@@ -247,6 +247,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useIsUploadEnabled(props.isUploadEnabled);
 
   useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled);
+  useIsRomUserAllowedToComment(props.isRomUserAllowedToComment);
 
   useIsAiEnabled(props.aiEnabled);
 

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

@@ -30,10 +30,52 @@ const {
  *
  *  components:
  *    schemas:
+ *      AttachmentPaginateResult:
+ *        description: AttachmentPaginateResult
+ *        type: object
+ *        properties:
+ *          docs:
+ *            type: array
+ *            items:
+ *              $ref: '#/components/schemas/Attachment'
+ *          totalDocs:
+ *            type: number
+ *            example: 1
+ *          limit:
+ *            type: number
+ *            example: 20
+ *          totalPages:
+ *            type: number
+ *            example: 1
+ *          page:
+ *            type: number
+ *            example: 1
+ *          offset:
+ *            type: number
+ *            example: 0
+ *          prevPage:
+ *            type: number
+ *            example: null
+ *          nextPage:
+ *            type: number
+ *            example: null
+ *          hasNextPage:
+ *            type: boolean
+ *            example: false
+ *          hasPrevPage:
+ *            type: boolean
+ *            example: false
+ *          pagingCounter:
+ *            type: number
+ *            example: 1
  *      Attachment:
  *        description: Attachment
  *        type: object
  *        properties:
+ *          id:
+ *            type: string
+ *            description: attachment ID
+ *            example: 5e0734e072560e001761fa67
  *          _id:
  *            type: string
  *            description: attachment ID
@@ -42,6 +84,10 @@ const {
  *            type: number
  *            description: attachment version
  *            example: 0
+ *          attachmentType:
+ *            type: string
+ *            description: attachment type
+ *            example: WIKI_PAGE
  *          fileFormat:
  *            type: string
  *            description: file format in MIME
@@ -55,6 +101,7 @@ const {
  *            description: original file name
  *            example: file.txt
  *          creator:
+ *            type: object
  *            $ref: '#/components/schemas/User'
  *          page:
  *            type: string
@@ -64,14 +111,14 @@ const {
  *            type: string
  *            description: date created at
  *            example: 2010-01-01T00:00:00.000Z
+ *          temporaryUrlExpiredAt:
+ *            type: string
+ *            description: temporary URL expired at
+ *            example: 2024-11-27T00:59:59.962Z
  *          fileSize:
  *            type: number
  *            description: file size
  *            example: 3494332
- *          url:
- *            type: string
- *            description: attachment URL
- *            example: http://localhost/files/5e0734e072560e001761fa67
  *          filePathProxied:
  *            type: string
  *            description: file path proxied
@@ -80,8 +127,11 @@ const {
  *            type: string
  *            description: download path proxied
  *            example: "/download/5e0734e072560e001761fa67"
+ *          temporaryUrlCached:
+ *            type: string
+ *            description: temporary URL cached
+ *            example: "https://example.com/attachment/5e0734e072560e001761fa67"
  */
-
 module.exports = (crowi) => {
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
@@ -117,16 +167,35 @@ module.exports = (crowi) => {
    *      get:
    *        tags: [Attachment]
    *        description: Get attachment list
-   *        responses:
-   *          200:
-   *            description: Return attachment list
    *        parameters:
-   *          - name: page_id
+   *          - name: pageId
    *            in: query
    *            required: true
    *            description: page id
    *            schema:
    *              type: string
+   *          - name: pageNumber
+   *            in: query
+   *            required: false
+   *            description: page number
+   *            schema:
+   *              type: number
+   *              example: 1
+   *          - name: limit
+   *            in: query
+   *            required: false
+   *            description: limit
+   *            schema:
+   *              type: number
+   *              example: 10
+   *        responses:
+   *          200:
+   *            description: Return attachment list
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  $ref: '#/components/schemas/AttachmentPaginateResult'
    */
   router.get('/list', accessTokenParser, loginRequired, validator.retrieveAttachments, apiV3FormValidator, async(req, res) => {
 
@@ -202,11 +271,6 @@ module.exports = (crowi) => {
    *          500:
    *            $ref: '#/components/responses/500'
    */
-  /**
-   * @api {get} /attachment/limit get available capacity of uploaded file with GridFS
-   * @apiName AddAttachment
-   * @apiGroup Attachment
-   */
   router.get('/limit', accessTokenParser, loginRequiredStrictly, validator.retrieveFileLimit, apiV3FormValidator, async(req, res) => {
     const { fileUploadService } = crowi;
     const fileSize = Number(req.query.fileSize);
@@ -234,10 +298,7 @@ module.exports = (crowi) => {
    *              schema:
    *                properties:
    *                  page_id:
-   *                    nullable: true
-   *                    type: string
-   *                  path:
-   *                    nullable: true
+   *                    nullable: false
    *                    type: string
    *                  file:
    *                    type: string
@@ -250,10 +311,7 @@ module.exports = (crowi) => {
    *              schema:
    *                properties:
    *                  page_id:
-   *                    nullable: true
-   *                    type: string
-   *                  path:
-   *                    nullable: true
+   *                    nullable: false
    *                    type: string
    *                  file:
    *                    type: string
@@ -273,26 +331,13 @@ module.exports = (crowi) => {
    *                      $ref: '#/components/schemas/Page'
    *                    attachment:
    *                      $ref: '#/components/schemas/Attachment'
-   *                    url:
-   *                      $ref: '#/components/schemas/Attachment/properties/url'
-   *                    pageCreated:
-   *                      type: boolean
-   *                      description: whether the page was created
-   *                      example: false
+   *                    revision:
+   *                      type: string
    *          403:
    *            $ref: '#/components/responses/403'
    *          500:
    *            $ref: '#/components/responses/500'
    */
-  /**
-   * @api {post} /attachment Add attachment to the page
-   * @apiName AddAttachment
-   * @apiGroup Attachment
-   *
-   * @apiParam {String} page_id
-   * @apiParam {String} path
-   * @apiParam {File} file
-   */
   router.post('/', uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly, excludeReadOnlyUser,
     validator.retrieveAddAttachment, apiV3FormValidator, addActivity,
     async(req, res) => {
@@ -342,6 +387,13 @@ module.exports = (crowi) => {
    *        responses:
    *          200:
    *            description: Return attachment
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    attachment:
+   *                      $ref: '#/components/schemas/Attachment'
    *        parameters:
    *          - name: id
    *            in: path

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

@@ -16,6 +16,85 @@ const express = require('express');
 
 const router = express.Router();
 
+/**
+ * @swagger
+ *
+ *  components:
+ *    schemas:
+ *      BookmarkFolder:
+ *        description: Bookmark Folder
+ *        type: object
+ *        properties:
+ *          _id:
+ *            type: string
+ *            description: Bookmark Folder ID
+ *          __v:
+ *            type: number
+ *            description: Version of the bookmark folder
+ *          name:
+ *            type: string
+ *            description: Name of the bookmark folder
+ *          owner:
+ *            type: string
+ *            description: Owner user ID of the bookmark folder
+ *          bookmarks:
+ *            type: array
+ *            items:
+ *              type: object
+ *              properties:
+ *                _id:
+ *                  type: string
+ *                  description: Bookmark ID
+ *                user:
+ *                  type: string
+ *                  description: User ID of the bookmarker
+ *                createdAt:
+ *                  type: string
+ *                  description: Date and time when the bookmark was created
+ *                __v:
+ *                  type: number
+ *                  description: Version of the bookmark
+ *                page:
+ *                  description: Pages that are bookmarked in the folder
+ *                  allOf:
+ *                    - $ref: '#/components/schemas/Page'
+ *                    - type: object
+ *                      properties:
+ *                        id:
+ *                          type: string
+ *                          description: Page ID
+ *                          example: "671b5cd38d45e62b52217ff8"
+ *                        parent:
+ *                          type: string
+ *                          description: Parent page ID
+ *                          example: 669a5aa48d45e62b521d00da
+ *                        descendantCount:
+ *                          type: number
+ *                          description: Number of descendants
+ *                          example: 0
+ *                        isEmpty:
+ *                          type: boolean
+ *                          description: Whether the page is empty
+ *                          example: false
+ *                        grantedGroups:
+ *                          type: array
+ *                          description: List of granted groups
+ *                          items:
+ *                            type: string
+ *                        creator:
+ *                          type: string
+ *                          description: Creator user ID
+ *                          example: "669a5aa48d45e62b521d00e4"
+ *                        latestRevisionBodyLength:
+ *                          type: number
+ *                          description: Length of the latest revision body
+ *                          example: 241
+ *          childFolder:
+ *            type: array
+ *            items:
+ *              type: object
+ *              $ref: '#/components/schemas/BookmarkFolder'
+ */
 const validator = {
   bookmarkFolder: [
     body('name').isString().withMessage('name must be a string'),
@@ -42,7 +121,40 @@ const validator = {
 module.exports = (crowi) => {
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
 
-  // Create new bookmark folder
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder:
+   *      post:
+   *        tags: [BookmarkFolders]
+   *        operationId: createBookmarkFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Create bookmark folder
+   *        description: Create a new bookmark folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  name:
+   *                    type: string
+   *                    description: Name of the bookmark folder
+   *                    nullable: false
+   *                  parent:
+   *                    type: string
+   *                    description: Parent folder ID
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.post('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, apiV3FormValidator, async(req, res) => {
     const owner = req.user?._id;
     const { name, parent } = req.body;
@@ -64,7 +176,37 @@ module.exports = (crowi) => {
     }
   });
 
-  // List bookmark folders and child
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/list/{userId}:
+   *      get:
+   *        tags: [BookmarkFolders]
+   *        operationId: listBookmarkFolders
+   *        security:
+   *          - api_key: []
+   *        summary: List bookmark folders of a user
+   *        description: List bookmark folders of a user
+   *        parameters:
+   *         - name: userId
+   *           in: path
+   *           required: true
+   *           description: User ID
+   *           schema:
+   *             type: string
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolderItems:
+   *                      type: array
+   *                      items:
+   *                        type: object
+   *                        $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.get('/list/:userId', accessTokenParser, loginRequiredStrictly, async(req, res) => {
     const { userId } = req.params;
 
@@ -123,7 +265,36 @@ module.exports = (crowi) => {
     }
   });
 
-  // Delete bookmark folder and children
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/{id}:
+   *      delete:
+   *        tags: [BookmarkFolders]
+   *        operationId: deleteBookmarkFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Delete bookmark folder
+   *        description: Delete a bookmark folder and its children
+   *        parameters:
+   *         - name: id
+   *           in: path
+   *           required: true
+   *           description: Bookmark Folder ID
+   *           schema:
+   *             type: string
+   *        responses:
+   *          200:
+   *            description: Deleted successfully
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    deletedCount:
+   *                      type: number
+   *                      description: Number of deleted folders
+   *                      example: 1
+   */
   router.delete('/:id', accessTokenParser, loginRequiredStrictly, async(req, res) => {
     const { id } = req.params;
     try {
@@ -137,6 +308,49 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder:
+   *      put:
+   *        tags: [BookmarkFolders]
+   *        operationId: updateBookmarkFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Update bookmark folder
+   *        description: Update a bookmark folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  bookmarkFolderId:
+   *                    type: string
+   *                    description: Bookmark Folder ID
+   *                  name:
+   *                    type: string
+   *                    description: Name of the bookmark folder
+   *                    nullable: false
+   *                  parent:
+   *                    type: string
+   *                    description: Parent folder ID
+   *                  childFolder:
+   *                    type: array
+   *                    description: Child folders
+   *                    items:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.put('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, async(req, res) => {
     const {
       bookmarkFolderId, name, parent, childFolder,
@@ -151,6 +365,41 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/add-boookmark-to-folder:
+   *      post:
+   *        tags: [BookmarkFolders]
+   *        operationId: addBookmarkToFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Update bookmark folder
+   *        description: Update a bookmark folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  pageId:
+   *                    type: string
+   *                    description: Page ID
+   *                    nullable: false
+   *                  folderId:
+   *                    type: string
+   *                    description: Folder ID
+   *                    nullable: true
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.post('/add-boookmark-to-folder', accessTokenParser, loginRequiredStrictly, validator.bookmarkPage, apiV3FormValidator, async(req, res) => {
     const userId = req.user?._id;
     const { pageId, folderId } = req.body;
@@ -166,6 +415,40 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/update-bookmark:
+   *      put:
+   *        tags: [BookmarkFolders]
+   *        operationId: updateBookmarkInFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Update bookmark in folder
+   *        description: Update a bookmark in a folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  pageId:
+   *                    type: string
+   *                    description: Page ID
+   *                    nullable: false
+   *                  status:
+   *                    type: string
+   *                    description: Bookmark status
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.put('/update-bookmark', accessTokenParser, loginRequiredStrictly, validator.bookmark, async(req, res) => {
     const { pageId, status } = req.body;
     const userId = req.user?._id;

+ 352 - 19
apps/app/src/server/routes/apiv3/customize-setting.js

@@ -36,7 +36,7 @@ const router = express.Router();
  *        description: CustomizeTheme
  *        type: object
  *        properties:
- *          themeType:
+ *          theme:
  *            type: string
  *      CustomizeFunction:
  *        description: CustomizeFunction
@@ -60,6 +60,14 @@ const router = express.Router();
  *        description: CustomizeHighlight
  *        type: object
  *        properties:
+ *          highlightJsStyle:
+ *            type: string
+ *          highlightJsStyleBorder:
+ *            type: boolean
+ *      CustomizeHighlightResponse:
+ *        description: CustomizeHighlight Response
+ *        type: object
+ *        properties:
  *          styleName:
  *            type: string
  *          styleBorder:
@@ -88,6 +96,99 @@ const router = express.Router();
  *        properties:
  *          customizeScript:
  *            type: string
+ *      CustomizeSetting:
+ *        description: Customize Setting
+ *        type: object
+ *        properties:
+ *          isEnabledTimeline:
+ *            type: boolean
+ *          isEnabledAttachTitleHeader:
+ *            type: boolean
+ *          pageLimitationS:
+ *            type: number
+ *          pageLimitationM:
+ *            type: number
+ *          pageLimitationL:
+ *            type: number
+ *          pageLimitationXL:
+ *            type: number
+ *          isEnabledStaleNotification:
+ *            type: boolean
+ *          isAllReplyShown:
+ *            type: boolean
+ *          isSearchScopeChildrenAsDefault:
+ *            type: boolean
+ *          isEnabledMarp:
+ *            type: boolean
+ *          styleName:
+ *            type: string
+ *          styleBorder:
+ *            type: string
+ *          customizeTitle:
+ *            type: string
+ *          customizeScript:
+ *            type: string
+ *          customizeCss:
+ *            type: string
+ *          customizeNoscript:
+ *            type: string
+ *      ThemesMetadata:
+ *        type: object
+ *        properties:
+ *          name:
+ *            type: string
+ *            description: The name of the plugin theme.
+ *          manifestKey:
+ *            type: string
+ *            description: Path to the theme manifest file.
+ *          schemeType:
+ *            type: string
+ *            description: The color scheme type (e.g., light or dark).
+ *          lightBg:
+ *            type: string
+ *            description: Light mode background color (hex).
+ *          darkBg:
+ *            type: string
+ *            description: Dark mode background color (hex).
+ *          lightSidebar:
+ *            type: string
+ *            description: Light mode sidebar color (hex).
+ *          darkSidebar:
+ *            type: string
+ *            description: Dark mode sidebar color (hex).
+ *          lightIcon:
+ *            type: string
+ *            description: Light mode icon color (hex).
+ *          darkIcon:
+ *            type: string
+ *            description: Dark mode icon color (hex).
+ *          createBtn:
+ *            type: string
+ *            description: Color of the create button (hex).
+ *      CustomizeSidebar:
+ *        description: Customize Sidebar
+ *        type: object
+ *        properties:
+ *          isSidebarCollapsedMode:
+ *            type: boolean
+ *            description: The flag whether sidebar is collapsed mode or not.
+ *          isSidebarClosedAtDockMode:
+ *            type: boolean
+ *            description: The flag whether sidebar is closed at dock mode or not.
+ *      CustomizePresentation:
+ *        description: Customize Presentation
+ *        type: object
+ *        properties:
+ *          isEnabledMarp:
+ *            type: boolean
+ *            description: The flag whether Marp is enabled or not.
+ *      CustomizeLogo:
+ *        description: Customize Logo
+ *        type: object
+ *        properties:
+ *          isDefaultLogo:
+ *            type: boolean
+ *            description: The flag whether the logo is default or not.
  */
 module.exports = (crowi) => {
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
@@ -153,6 +254,8 @@ module.exports = (crowi) => {
    *    /customize-setting:
    *      get:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: getCustomizeSetting
    *        summary: /customize-setting
    *        description: Get customize parameters
@@ -166,6 +269,7 @@ module.exports = (crowi) => {
    *                    customizeParams:
    *                      type: object
    *                      description: customize params
+   *                      $ref: '#/components/schemas/CustomizeSetting'
    */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
     const customizeParams = {
@@ -196,6 +300,8 @@ module.exports = (crowi) => {
    *    /customize-setting/layout:
    *      get:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: getLayoutCustomizeSetting
    *        summary: /customize-setting/layout
    *        description: Get layout
@@ -208,7 +314,6 @@ module.exports = (crowi) => {
    *                  $ref: '#/components/schemas/CustomizeLayout'
    */
   router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => {
-
     try {
       const isContainerFluid = await crowi.configManager.getConfig('crowi', 'customize:isContainerFluid');
       return res.apiv3({ isContainerFluid });
@@ -241,7 +346,12 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeLayout'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      type: object
+   *                      description: customized params
+   *                      $ref: '#/components/schemas/CustomizeLayout'
    */
   router.put('/layout', loginRequiredStrictly, adminRequired, addActivity, validator.layout, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -266,6 +376,34 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/theme:
+   *      get:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: getThemeCustomizeSetting
+   *        summary: /customize-setting/theme
+   *        description: Get theme
+   *        responses:
+   *          200:
+   *            description: Succeeded to get layout
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    currentTheme:
+   *                      type: string
+   *                      description: The current theme name.
+   *                    pluginThemesMetadatas:
+   *                      type: array
+   *                      description: Metadata for available plugin themes.
+   *                      items:
+   *                        $ref: '#/components/schemas/ThemesMetadata'
+   */
   router.get('/theme', loginRequiredStrictly, async(req, res) => {
 
     try {
@@ -293,6 +431,8 @@ module.exports = (crowi) => {
    *    /customize-setting/theme:
    *      put:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateThemeCustomizeSetting
    *        summary: /customize-setting/theme
    *        description: Update theme
@@ -308,7 +448,10 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeTheme'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeTheme'
    */
   router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -332,7 +475,25 @@ module.exports = (crowi) => {
     }
   });
 
-  // sidebar
+  /**
+   * @swagger
+   *
+   *    /customize-setting/sidebar:
+   *      get:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: getCustomeSettingSidebar
+   *        summary: /customize-setting/sidebar
+   *        description: Get sidebar
+   *        responses:
+   *          200:
+   *            description: Succeeded to get sidebar
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/CustomizeSidebar'
+   */
   router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
 
     try {
@@ -347,6 +508,34 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/sidebar:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: updateCustomizeSettingSidebar
+   *        summary: /customize-setting/sidebar
+   *        description: Update sidebar
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/CustomizeSidebar'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update sidebar
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeSidebar'
+   */
   router.put('/sidebar', loginRequiredStrictly, adminRequired, validator.sidebar, apiV3FormValidator, addActivity, async(req, res) => {
     const requestParams = {
       'customize:isSidebarCollapsedMode': req.body.isSidebarCollapsedMode,
@@ -377,6 +566,8 @@ module.exports = (crowi) => {
    *    /customize-setting/function:
    *      put:
    *        tags: [CustomizeSetting]
+   *        security:
+   *         - cookieAuth: []
    *        operationId: updateFunctionCustomizeSetting
    *        summary: /customize-setting/function
    *        description: Update function
@@ -392,7 +583,10 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeFunction'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeFunction'
    */
   router.put('/function', loginRequiredStrictly, adminRequired, addActivity, validator.function, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -432,6 +626,34 @@ module.exports = (crowi) => {
   });
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/presentation:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *         - cookieAuth: []
+   *        operationId: updatePresentationCustomizeSetting
+   *        summary: /customize-setting/presentation
+   *        description: Update presentation
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/CustomizePresentation'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update presentation
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizePresentation'
+   */
   router.put('/presentation', loginRequiredStrictly, adminRequired, addActivity, validator.CustomizePresentation, apiV3FormValidator, async(req, res) => {
     const requestParams = {
       'customize:isEnabledMarp': req.body.isEnabledMarp,
@@ -459,6 +681,8 @@ module.exports = (crowi) => {
    *    /customize-setting/highlight:
    *      put:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateHighlightCustomizeSetting
    *        summary: /customize-setting/highlight
    *        description: Update highlight
@@ -474,7 +698,10 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeHighlight'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeHighlightResponse'
    */
   router.put('/highlight', loginRequiredStrictly, adminRequired, addActivity, validator.highlight, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -505,9 +732,11 @@ module.exports = (crowi) => {
    *    /customize-setting/customizeTitle:
    *      put:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeTitleCustomizeSetting
    *        summary: /customize-setting/customizeTitle
-   *        description: Update customizeTitle
+   *        description: Update title
    *        requestBody:
    *          required: true
    *          content:
@@ -520,7 +749,10 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeTitle'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeTitle'
    */
   router.put('/customize-title', loginRequiredStrictly, adminRequired, addActivity, validator.customizeTitle, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -552,9 +784,11 @@ module.exports = (crowi) => {
    *    /customize-setting/customize-noscript:
    *      put:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeNoscriptCustomizeSetting
    *        summary: /customize-setting/customize-noscript
-   *        description: Update customizeNoscript
+   *        description: Update noscript
    *        requestBody:
    *          required: true
    *          content:
@@ -567,7 +801,10 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeNoscript'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeNoscript'
    */
   router.put('/customize-noscript', loginRequiredStrictly, adminRequired, addActivity, validator.customizeNoscript, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -592,12 +829,14 @@ module.exports = (crowi) => {
   /**
    * @swagger
    *
-   *    /customize-setting/customizeCss:
+   *    /customize-setting/customize-css:
    *      put:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeCssCustomizeSetting
-   *        summary: /customize-setting/customizeCss
-   *        description: Update customizeCss
+   *        summary: /customize-setting/customize-css
+   *        description: Update customize css
    *        requestBody:
    *          required: true
    *          content:
@@ -610,7 +849,10 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeCss'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeCss'
    */
   router.put('/customize-css', loginRequiredStrictly, adminRequired, addActivity, validator.customizeCss, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -638,12 +880,14 @@ module.exports = (crowi) => {
   /**
    * @swagger
    *
-   *    /customize-setting/customizeScript:
+   *    /customize-setting/customize-script:
    *      put:
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeScriptCustomizeSetting
-   *        summary: /customize-setting/customizeScript
-   *        description: Update customizeScript
+   *        summary: /customize-setting/customize-script
+   *        description: Update customize script
    *        requestBody:
    *          required: true
    *          content:
@@ -656,7 +900,10 @@ module.exports = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeScript'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeScript'
    */
   router.put('/customize-script', loginRequiredStrictly, adminRequired, addActivity, validator.customizeScript, apiV3FormValidator, async(req, res) => {
     const requestParams = {
@@ -678,6 +925,34 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/customize-logo:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: updateCustomizeLogoCustomizeSetting
+   *        summary: /customize-setting/customize-logo
+   *        description: Update customize logo
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/CustomizeLogo'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update customize logo
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeLogo'
+   */
   router.put('/customize-logo', loginRequiredStrictly, adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
 
     const {
@@ -701,6 +976,45 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/upload-brand-logo:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: uploadBrandLogoCustomizeSetting
+   *        summary: /customize-setting/upload-brand-logo
+   *        description: Upload brand logo
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            multipart/form-data:
+   *              schema:
+   *               type: object
+   *               properties:
+   *                 file:
+   *                   format: binary
+   *        responses:
+   *          200:
+   *            description: Succeeded to upload brand logo
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    attachment:
+   *                      allOf:
+   *                        - $ref: '#/components/schemas/Attachment'
+   *                        - type: object
+   *                          properties:
+   *                            creator:
+   *                              type: string
+   *                            page: {}
+   *                            temporaryUrlExpiredAt: {}
+   *                            temporaryUrlCached: {}
+   */
   router.post('/upload-brand-logo', uploads.single('file'), loginRequiredStrictly,
     adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
 
@@ -738,6 +1052,25 @@ module.exports = (crowi) => {
       return res.apiv3({ attachment });
     });
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/delete-brand-logo:
+   *      delete:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: deleteBrandLogoCustomizeSetting
+   *        summary: /customize-setting/delete-brand-logo
+   *        description: Delete brand logo
+   *        responses:
+   *          200:
+   *            description: Succeeded to delete brand logo
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  additionalProperties: false
+   */
   router.delete('/delete-brand-logo', loginRequiredStrictly, adminRequired, async(req, res) => {
 
     const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });

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

@@ -20,22 +20,100 @@ const router = express.Router();
  *  components:
  *    schemas:
  *      ExportStatus:
- *        description: ExportStatus
  *        type: object
  *        properties:
  *          zipFileStats:
  *            type: array
  *            items:
- *              type: object
- *              description: the property of each file
+ *              $ref: '#/components/schemas/ExportZipFileStat'
+ *          isExporting:
+ *            type: boolean
  *          progressList:
+ *            type: [array, null]
+ *            items:
+ *              type: string
+ *      ExportZipFileStat:
+ *        type: object
+ *        properties:
+ *          meta:
+ *            $ref: '#/components/schemas/ExportMeta'
+ *          fileName:
+ *            type: string
+ *          zipFilePath:
+ *            type: string
+ *          fileStat:
+ *            $ref: '#/components/schemas/ExportFileStat'
+ *          innerFileStats:
  *            type: array
  *            items:
- *              type: object
- *              description: progress data for each exporting collections
- *          isExporting:
- *            type: boolean
- *            description: whether the current exporting job exists or not
+ *              $ref: '#/components/schemas/ExportInnerFileStat'
+ *      ExportMeta:
+ *        type: object
+ *        properties:
+ *          version:
+ *            type: string
+ *          url:
+ *            type: string
+ *          passwordSeed:
+ *            type: string
+ *          exportedAt:
+ *            type: string
+ *            format: date-time
+ *          envVars:
+ *            type: object
+ *            additionalProperties:
+ *              type: string
+ *      ExportFileStat:
+ *        type: object
+ *        properties:
+ *          dev:
+ *            type: integer
+ *          mode:
+ *            type: integer
+ *          nlink:
+ *            type: integer
+ *          uid:
+ *            type: integer
+ *          gid:
+ *            type: integer
+ *          rdev:
+ *            type: integer
+ *          blksize:
+ *            type: integer
+ *          ino:
+ *            type: integer
+ *          size:
+ *            type: integer
+ *          blocks:
+ *            type: integer
+ *          atime:
+ *            type: string
+ *            format: date-time
+ *          mtime:
+ *            type: string
+ *            format: date-time
+ *          ctime:
+ *            type: string
+ *            format: date-time
+ *          birthtime:
+ *            type: string
+ *            format: date-time
+ *      ExportInnerFileStat:
+ *        type: object
+ *        properties:
+ *          fileName:
+ *            type: string
+ *          collectionName:
+ *            type: string
+ *          meta:
+ *           progressList:
+ *             type: array
+ *             items:
+ *               type: object
+ *               description: progress data for each exporting collections
+ *           isExporting:
+ *             type: boolean
+ *             description: whether the current exporting job exists or not
  */
 
 module.exports = (crowi) => {
@@ -84,6 +162,9 @@ module.exports = (crowi) => {
    *            application/json:
    *              schema:
    *                properties:
+   *                  ok:
+   *                    type: boolean
+   *                    description: whether the request is succeeded or not
    *                  status:
    *                    $ref: '#/components/schemas/ExportStatus'
    */
@@ -106,6 +187,17 @@ module.exports = (crowi) => {
    *      operationId: createExport
    *      summary: /export
    *      description: generate zipped jsons for collections
+   *      requestBody:
+   *        content:
+   *          application/json:
+   *            schema:
+   *              properties:
+   *                collections:
+   *                  type: array
+   *                  items:
+   *                    type: string
+   *                    description: the collections to export
+   *                    example: ["pages", "tags"]
    *      responses:
    *        200:
    *          description: a zip file is generated
@@ -113,8 +205,9 @@ module.exports = (crowi) => {
    *            application/json:
    *              schema:
    *                properties:
-   *                  status:
-   *                    $ref: '#/components/schemas/ExportStatus'
+   *                  ok:
+   *                    type: boolean
+   *                    description: whether the request is succeeded
    */
   router.post('/', accessTokenParser, loginRequired, adminRequired, addActivity, async(req, res) => {
     // TODO: add express validator
@@ -161,6 +254,10 @@ module.exports = (crowi) => {
    *            application/json:
    *              schema:
    *                type: object
+   *                properties:
+   *                  ok:
+   *                    type: boolean
+   *                    description: whether the request is succeeded
    */
   router.delete('/:fileName', accessTokenParser, loginRequired, adminRequired, validator.deleteFile, apiV3FormValidator, addActivity, async(req, res) => {
     // TODO: add express validator

+ 78 - 0
apps/app/src/server/routes/apiv3/forgot-password.js

@@ -18,6 +18,25 @@ const logger = loggerFactory('growi:routes:apiv3:forgotPassword'); // eslint-dis
 const express = require('express');
 const { body } = require('express-validator');
 
+/**
+ * @swagger
+ *
+ * components:
+ *   schemas:
+ *     PasswordResetRequest:
+ *       type: object
+ *       properties:
+ *         email:
+ *           type: string
+ *           format: email
+ *     PasswordResetResponse:
+ *       type: object
+ *       properties:
+ *         message:
+ *           type: string
+ *         error:
+ *           type: string
+*/
 
 const router = express.Router();
 
@@ -69,6 +88,34 @@ module.exports = (crowi) => {
     });
   }
 
+  /**
+   * @swagger
+   *
+   *  /forgot-password:
+   *    post:
+   *      summary: Request password reset
+   *      tags: [Users]
+   *      security:
+   *        -
+   *      requestBody:
+   *        required: true
+   *        content:
+   *          application/json:
+   *            schema:
+   *              type: object
+   *              properties:
+   *                email:
+   *                  type: string
+   *                  format: email
+   *                  description: Email address of the user requesting password reset
+   *      responses:
+   *        '200':
+   *          description: Password reset request processed
+   *          content:
+   *            application/json:
+   *              schema:
+   *                type: object
+   */
   router.post('/', checkPassportStrategyMiddleware, validator.email, apiV3FormValidator, addActivity, async(req, res) => {
     const { email } = req.body;
     const locale = configManager.getConfig('crowi', 'app:globalLang');
@@ -103,6 +150,37 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *  /forgot-password:
+   *    put:
+   *      summary: Reset password
+   *      tags: [Users]
+   *      security:
+   *        -
+   *      requestBody:
+   *        required: true
+   *        content:
+   *          application/json:
+   *            schema:
+   *              type: object
+   *              properties:
+   *                newPassword:
+   *                  type: string
+   *                  format: password
+   *                  description: New password
+   *      responses:
+   *        '200':
+   *          description: Password reset successful
+   *          content:
+   *            application/json:
+   *              schema:
+   *                type: object
+   *                properties:
+   *                  userData:
+   *                    $ref: '#/components/schemas/User'
+   */
   // eslint-disable-next-line max-len
   router.put('/', checkPassportStrategyMiddleware, injectResetOrderByTokenMiddleware, validator.password, apiV3FormValidator, addActivity, async(req, res) => {
     const { passwordResetOrder } = req;

+ 5 - 6
apps/app/src/server/service/export.js

@@ -8,7 +8,7 @@ const logger = loggerFactory('growi:services:ExportService'); // eslint-disable-
 const fs = require('fs');
 const path = require('path');
 const { Transform } = require('stream');
-const { pipeline } = require('stream/promises');
+const { pipeline, finished } = require('stream/promises');
 
 const archiver = require('archiver');
 const mongoose = require('mongoose');
@@ -107,7 +107,7 @@ class ExportService {
     writeStream.write(JSON.stringify(metaData));
     writeStream.close();
 
-    await pipeline([writeStream]);
+    await finished(writeStream);
 
     return metaJson;
   }
@@ -349,13 +349,12 @@ class ExportService {
 
     const output = fs.createWriteStream(zipFile);
 
-    // pipe archive data to the file
-    const stream = pipeline(archive, output);
-
     // finalize the archive (ie we are done appending files but streams have to finish yet)
     // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
     archive.finalize();
-    await stream;
+
+    // pipe archive data to the file
+    await pipeline(archive, output);
 
     logger.info(`zipped GROWI data into ${zipFile} (${archive.pointer()} bytes)`);
 

+ 3 - 3
apps/app/src/server/service/growi-bridge/index.ts

@@ -1,7 +1,7 @@
 import fs from 'fs';
 import path from 'path';
 import { pipeline } from 'stream';
-import { pipeline as pipelinePromise } from 'stream/promises';
+import { finished } from 'stream/promises';
 
 import unzipStream, { type Entry } from 'unzip-stream';
 
@@ -80,7 +80,7 @@ class GrowiBridgeService {
 
     const readStream = fs.createReadStream(zipFile);
     const parseStream = unzipStream.Parse();
-    const unzipEntryStream = pipeline(readStream, parseStream);
+    const unzipEntryStream = pipeline(readStream, parseStream, () => {});
 
     let tapPromise;
 
@@ -103,7 +103,7 @@ class GrowiBridgeService {
     });
 
     try {
-      await pipelinePromise([unzipEntryStream]);
+      await finished(unzipEntryStream);
       await tapPromise;
     }
     // if zip is broken

+ 5 - 5
apps/app/src/server/service/import/import.ts

@@ -2,7 +2,7 @@ import fs from 'fs';
 import path from 'path';
 import type { EventEmitter } from 'stream';
 import { Writable, Transform, pipeline } from 'stream';
-import { pipeline as pipelinePromise } from 'stream/promises';
+import { finished, pipeline as pipelinePromise } from 'stream/promises';
 
 import JSONStream from 'JSONStream';
 import gc from 'expose-gc/function';
@@ -344,10 +344,10 @@ export class ImportService {
   async unzip(zipFile) {
     const readStream = fs.createReadStream(zipFile);
     const parseStream = unzipStream.Parse();
-    const unzipStreamPipe = pipeline(readStream, parseStream);
+    const unzipEntryStream = pipeline(readStream, parseStream, () => {});
     const files: string[] = [];
 
-    const unzipEntryStream = unzipStreamPipe.on('entry', (/** @type {Entry} */ entry) => {
+    unzipEntryStream.on('entry', (/** @type {Entry} */ entry) => {
       const fileName = entry.path;
       // https://regex101.com/r/mD4eZs/6
       // prevent from unexpecting attack doing unzip file (path traversal attack)
@@ -365,12 +365,12 @@ export class ImportService {
       else {
         const jsonFile = path.join(this.baseDir, fileName);
         const writeStream = fs.createWriteStream(jsonFile, { encoding: this.growiBridgeService.getEncoding() });
-        pipeline(entry, writeStream);
+        pipeline(entry, writeStream, () => {});
         files.push(jsonFile);
       }
     });
 
-    await pipelinePromise([unzipEntryStream]);
+    await finished(unzipEntryStream);
 
     return files;
   }

+ 1 - 1
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "7.1.4-slackbot-proxy.0",
+  "version": "7.1.5-slackbot-proxy.0",
   "license": "MIT",
   "private": "true",
   "scripts": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "7.1.4",
+  "version": "7.1.5-RC.0",
   "description": "Team collaboration software using markdown",
   "license": "MIT",
   "private": "true",