Browse Source

Merge branch 'master' into fix/156799-156858-stop-growi-when-someone-create-deep-page

reiji-h 1 year ago
parent
commit
d6511f14a7

+ 14 - 1
CHANGELOG.md

@@ -1,9 +1,22 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.1.4...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.1.5...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v7.1.5](https://github.com/weseek/growi/compare/v7.1.4...v7.1.5) - 2024-12-13
+
+### 🚀 Improvement
+
+* imprv: Slim down the sidebar scrollbar (#9430) @reiji-h
+
+### 🐛 Bug Fixes
+
+* fix: Usage for stream pipeline of callback version (#9455) @reiji-h
+* fix: TypeError occurs during export (#9481) @miya
+* fix: Put `/` before the page name (#9471) @Ryosei-Fukushima
+* fix: Cannot comment when comments from rom user are allowed (#9472) @miya
+
 ## [v7.1.4](https://github.com/weseek/growi/compare/v7.1.3...v7.1.4) - 2024-11-26
 
 ### 🐛 Bug Fixes

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.1.5-RC.0",
+  "version": "7.1.6-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);
 

+ 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.5-slackbot-proxy.0",
+  "version": "7.1.6-slackbot-proxy.0",
   "license": "MIT",
   "private": "true",
   "scripts": {

+ 1 - 1
package.json

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