Sfoglia il codice sorgente

Updated API response schema to unify error responses, replaced V1ResponseOK with ApiResponseSuccess, and fixed 403 and 500 response references.

Yuki Takei 11 mesi fa
parent
commit
9ccedcac39

+ 121 - 10
apps/app/src/server/models/openapi/v1-response.js

@@ -3,18 +3,129 @@
  *
  *  components:
  *    schemas:
- *      V1ResponseOK:
- *        description: API is succeeded
- *        type: boolean
- *      V1Response:
- *        description: Response v1
+ *
+ *      # Common API Response Schemas (modern pattern)
+ *      ApiResponseBase:
  *        type: object
+ *        required:
+ *          - ok
  *        properties:
  *          ok:
- *            $ref: '#/components/schemas/V1ResponseOK'
+ *            type: boolean
+ *            description: Indicates if the request was successful
+ *
+ *      ApiResponseSuccess:
+ *        description: Successful API response
+ *        allOf:
+ *          - $ref: '#/components/schemas/ApiResponseBase'
+ *          - type: object
+ *            properties:
+ *              ok:
+ *                type: boolean
+ *                enum: [true]
+ *                example: true
+ *                description: Success indicator (always true for successful responses)
+ *
+ *      ApiResponseError:
+ *        description: Error API response
+ *        allOf:
+ *          - $ref: '#/components/schemas/ApiResponseBase'
+ *          - type: object
+ *            properties:
+ *              ok:
+ *                type: boolean
+ *                enum: [false]
+ *                example: false
+ *                description: Success indicator (always false for error responses)
+ *              error:
+ *                oneOf:
+ *                  - type: string
+ *                    description: Simple error message
+ *                    example: "Invalid parameter"
+ *                  - type: object
+ *                    description: Detailed error object
+ *                    example: { "code": "VALIDATION_ERROR", "message": "Field validation failed" }
+ *                description: Error message or error object containing details about the failure
+ *
  *    responses:
- *      403:
- *        description: 'Forbidden'
- *      500:
- *        description: 'Internal Server Error'
+ *      # Common error responses
+ *      BadRequest:
+ *        description: Bad request
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            examples:
+ *              missingParameter:
+ *                summary: Missing required parameter
+ *                value:
+ *                  ok: false
+ *                  error: "Invalid parameter"
+ *              validationError:
+ *                summary: Validation error
+ *                value:
+ *                  ok: false
+ *                  error: "Validation failed"
+ *
+ *      Forbidden:
+ *        description: Forbidden - insufficient permissions
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            example:
+ *              ok: false
+ *              error: "Access denied"
+ *
+ *      NotFound:
+ *        description: Resource not found
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            examples:
+ *              resourceNotFound:
+ *                summary: Resource not found
+ *                value:
+ *                  ok: false
+ *                  error: "Resource not found"
+ *              notFoundOrForbidden:
+ *                summary: Resource not found or forbidden
+ *                value:
+ *                  ok: false
+ *                  error: "notfound_or_forbidden"
+ *
+ *      Conflict:
+ *        description: Conflict
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            examples:
+ *              resourceConflict:
+ *                summary: Resource conflict
+ *                value:
+ *                  ok: false
+ *                  error: "Resource conflict"
+ *              outdated:
+ *                summary: Resource was updated by someone else
+ *                value:
+ *                  ok: false
+ *                  error: "outdated"
+ *              alreadyExists:
+ *                summary: Resource already exists
+ *                value:
+ *                  ok: false
+ *                  error: "already_exists"
+ *
+ *      InternalServerError:
+ *        description: Internal server error
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            example:
+ *              ok: false
+ *              error: "Internal server error"
+ *
  */

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

@@ -267,9 +267,9 @@ module.exports = (crowi) => {
    *                      description: uploadable
    *                      example: true
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   router.get('/limit', accessTokenParser, loginRequiredStrictly, validator.retrieveFileLimit, apiV3FormValidator, async(req, res) => {
     const { fileUploadService } = crowi;
@@ -333,9 +333,9 @@ module.exports = (crowi) => {
    *                    revision:
    *                      type: string
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   router.post('/', accessTokenParser, loginRequiredStrictly, excludeReadOnlyUser, uploads.single('file'),
     validator.retrieveAddAttachment, apiV3FormValidator, addActivity,

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

@@ -409,9 +409,9 @@ module.exports = (crowi) => {
    *                        revision:
    *                          $ref: '#/components/schemas/Revision'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   router.put('/', updatePageHandlersFactory(crowi));
 

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

@@ -1212,9 +1212,9 @@ module.exports = (crowi) => {
    *                          $ref: '#/components/schemas/User'
    *                        description: user list
    *            403:
-   *              $ref: '#/components/responses/403'
+   *              $ref: '#/components/responses/Forbidden'
    *            500:
-   *              $ref: '#/components/responses/500'
+   *              $ref: '#/components/responses/InternalServerError'
    */
   router.get('/list', accessTokenParser, loginRequired, async(req, res) => {
     const userIds = req.query.userIds ?? null;

+ 15 - 18
apps/app/src/server/routes/attachment/api.js

@@ -1,4 +1,3 @@
-
 import { SupportedAction } from '~/interfaces/activity';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import loggerFactory from '~/utils/logger';
@@ -220,15 +219,17 @@ export const routesFactory = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    attachment:
-   *                      $ref: '#/components/schemas/AttachmentProfile'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        attachment:
+   *                          $ref: '#/components/schemas/AttachmentProfile'
+   *                          description: The uploaded profile image attachment
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /attachments.uploadProfileImage Add attachment for profile image
@@ -298,13 +299,11 @@ export const routesFactory = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
+   *                  $ref: '#/components/schemas/ApiResponseSuccess'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /attachments.remove Remove attachments
@@ -363,13 +362,11 @@ export const routesFactory = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
+   *                  $ref: '#/components/schemas/ApiResponseSuccess'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /attachments.removeProfileImage Remove profile image attachments

+ 32 - 31
apps/app/src/server/routes/comment.js

@@ -1,4 +1,3 @@
-
 import { getIdStringForRef } from '@growi/core';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 
@@ -101,17 +100,19 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comments:
-   *                      type: array
-   *                      items:
-   *                        $ref: '#/components/schemas/Comment'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        comments:
+   *                          type: array
+   *                          items:
+   *                            $ref: '#/components/schemas/Comment'
+   *                          description: List of comments for the page revision
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /comments.get Get comments of the page of the revision
@@ -207,15 +208,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comment:
-   *                      $ref: '#/components/schemas/Comment'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        comment:
+   *                          $ref: '#/components/schemas/Comment'
+   *                          description: The newly created comment
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /comments.add Post comment for the page
@@ -353,15 +356,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comment:
-   *                      $ref: '#/components/schemas/Comment'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        comment:
+   *                          $ref: '#/components/schemas/Comment'
+   *                          description: The updated comment
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /comments.update Update comment dody
@@ -444,15 +449,11 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comment:
-   *                      $ref: '#/components/schemas/Comment'
+   *                  $ref: '#/components/schemas/ApiResponseSuccess'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /comments.remove Remove specified comment

+ 330 - 117
apps/app/src/server/routes/page.js

@@ -14,54 +14,6 @@ import UpdatePost from '../models/update-post';
  *    name: Pages
  */
 
-/**
- * @swagger
- *
- *  components:
- *    schemas:
- *
- *      UpdatePost:
- *        description: UpdatePost
- *        type: object
- *        properties:
- *          _id:
- *            type: string
- *            description: update post ID
- *            example: 5e0734e472560e001761fa68
- *          __v:
- *            type: number
- *            description: DB record version
- *            example: 0
- *          pathPattern:
- *            type: string
- *            description: path pattern
- *            example: /test
- *          patternPrefix:
- *            type: string
- *            description: patternPrefix prefix
- *            example: /
- *          patternPrefix2:
- *            type: string
- *            description: path
- *            example: test
- *          channel:
- *            type: string
- *            description: channel
- *            example: general
- *          provider:
- *            type: string
- *            description: provider
- *            enum:
- *              - slack
- *            example: slack
- *          creator:
- *            $ref: '#/components/schemas/User'
- *          createdAt:
- *            type: string
- *            description: date created at
- *            example: 2010-01-01T00:00:00.000Z
- */
-
 /* eslint-disable no-use-before-define */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = function(crowi, app) {
@@ -134,37 +86,57 @@ module.exports = function(crowi, app) {
   const validator = {};
 
   actions.api = api;
-  actions.validator = validator;
-
-  /**
+  actions.validator = validator; /**
    * @swagger
    *
-   *    /pages.getPageTag:
-   *      get:
-   *        tags: [Pages]
-   *        operationId: getPageTag
-   *        summary: /pages.getPageTag
-   *        description: Get page tag
-   *        parameters:
-   *          - in: query
-   *            name: pageId
-   *            schema:
-   *              $ref: '#/components/schemas/ObjectId'
-   *        responses:
-   *          200:
-   *            description: Succeeded to get page tags.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    tags:
-   *                      $ref: '#/components/schemas/Tags'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
+   * components:
+   *   schemas:
+   *     PageTagsData:
+   *       type: object
+   *       properties:
+   *         tags:
+   *           type: array
+   *           items:
+   *             type: string
+   *           description: Array of tag names associated with the page
+   *           example: ["javascript", "tutorial", "backend"]
+   *
+   *   responses:
+   *     PageTagsSuccess:
+   *       description: Successfully retrieved page tags
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageTagsData'
+   *
+   * /pages.getPageTag:
+   *   get:
+   *     tags: [Pages]
+   *     operationId: getPageTag
+   *     summary: Get page tags
+   *     description: Retrieve all tags associated with a specific page
+   *     parameters:
+   *       - in: query
+   *         name: pageId
+   *         required: true
+   *         description: Unique identifier of the page
+   *         schema:
+   *           type: string
+   *           format: ObjectId
+   *           example: "507f1f77bcf86cd799439011"
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageTagsSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       404:
+   *         $ref: '#/components/responses/NotFound'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /pages.getPageTag get page tags
@@ -187,32 +159,58 @@ module.exports = function(crowi, app) {
   /**
    * @swagger
    *
-   *    /pages.updatePost:
-   *      get:
-   *        tags: [Pages]
-   *        operationId: getUpdatePostPage
-   *        summary: /pages.updatePost
-   *        description: Get UpdatePost setting list
-   *        parameters:
-   *          - in: query
-   *            name: path
-   *            schema:
-   *              $ref: '#/components/schemas/PagePath'
-   *        responses:
-   *          200:
-   *            description: Succeeded to get UpdatePost setting list.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    updatePost:
-   *                      $ref: '#/components/schemas/UpdatePost'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
+   * components:
+   *   schemas:
+   *     UpdatePostData:
+   *       type: object
+   *       properties:
+   *         updatePost:
+   *           type: array
+   *           items:
+   *             type: string
+   *           description: Array of channel names for notifications
+   *           example: ["general", "development", "notifications"]
+   *
+   *   responses:
+   *     UpdatePostSuccess:
+   *       description: Successfully retrieved UpdatePost settings
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/UpdatePostData'
+   *
+   * /pages.updatePost:
+   *   get:
+   *     tags: [Pages]
+   *     operationId: getUpdatePost
+   *     summary: Get UpdatePost settings
+   *     description: Retrieve UpdatePost notification settings for a specific path
+   *     parameters:
+   *       - in: query
+   *         name: path
+   *         required: true
+   *         description: Page path to get UpdatePost settings for
+   *         schema:
+   *           type: string
+   *           example: "/user/example"
+   *         examples:
+   *           userPage:
+   *             value: "/user/john"
+   *             description: User page path
+   *           projectPage:
+   *             value: "/project/myproject"
+   *             description: Project page path
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/UpdatePostSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /pages.updatePost
@@ -254,12 +252,100 @@ module.exports = function(crowi, app) {
   ];
 
   /**
-   * @api {post} /pages.remove Remove page
-   * @apiName RemovePage
-   * @apiGroup Page
+   * @swagger
+   *
+   * components:
+   *   schemas:
+   *     PageRemoveData:
+   *       type: object
+   *       properties:
+   *         path:
+   *           type: string
+   *           description: Path of the deleted page
+   *           example: "/user/example"
+   *         isRecursively:
+   *           type: boolean
+   *           description: Whether deletion was recursive
+   *           example: true
+   *         isCompletely:
+   *           type: boolean
+   *           description: Whether deletion was complete
+   *           example: false
+   *
+   *   responses:
+   *     PageRemoveSuccess:
+   *       description: Page successfully deleted
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageRemoveData'
    *
-   * @apiParam {String} page_id Page Id.
-   * @apiParam {String} revision_id
+   * /pages.remove:
+   *   post:
+   *     tags: [Pages]
+   *     operationId: removePage
+   *     summary: Remove page
+   *     description: Delete a page either softly or completely, with optional recursive deletion
+   *     requestBody:
+   *       required: true
+   *       content:
+   *         application/json:
+   *           schema:
+   *             type: object
+   *             required:
+   *               - page_id
+   *             properties:
+   *               page_id:
+   *                 type: string
+   *                 format: ObjectId
+   *                 description: Unique identifier of the page to delete
+   *                 example: "507f1f77bcf86cd799439011"
+   *               revision_id:
+   *                 type: string
+   *                 format: ObjectId
+   *                 description: Revision ID for conflict detection
+   *                 example: "507f1f77bcf86cd799439012"
+   *               completely:
+   *                 type: boolean
+   *                 description: Whether to delete the page completely (true) or soft delete (false)
+   *                 default: false
+   *                 example: false
+   *               recursively:
+   *                 type: boolean
+   *                 description: Whether to delete child pages recursively
+   *                 default: false
+   *                 example: true
+   *           examples:
+   *             softDelete:
+   *               summary: Soft delete single page
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 revision_id: "507f1f77bcf86cd799439012"
+   *             recursiveDelete:
+   *               summary: Recursive soft delete
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 recursively: true
+   *             completeDelete:
+   *               summary: Complete deletion
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 completely: true
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageRemoveSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       404:
+   *         $ref: '#/components/responses/NotFound'
+   *       409:
+   *         $ref: '#/components/responses/Conflict'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   api.remove = async function(req, res) {
     const pageId = req.body.page_id;
@@ -365,11 +451,89 @@ module.exports = function(crowi, app) {
   ];
 
   /**
-   * @api {post} /pages.revertRemove Revert removed page
-   * @apiName RevertRemovePage
-   * @apiGroup Page
+   * @swagger
+   *
+   * components:
+   *   schemas:
+   *     PageRevertData:
+   *       type: object
+   *       properties:
+   *         page:
+   *           type: object
+   *           description: Restored page object
+   *           properties:
+   *             _id:
+   *               type: string
+   *               format: ObjectId
+   *               example: "507f1f77bcf86cd799439011"
+   *             path:
+   *               type: string
+   *               example: "/user/example"
+   *             title:
+   *               type: string
+   *               example: "Example Page"
+   *             status:
+   *               type: string
+   *               example: "published"
    *
-   * @apiParam {String} page_id Page Id.
+   *   responses:
+   *     PageRevertSuccess:
+   *       description: Page successfully restored
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageRevertData'
+   *
+   * /pages.revertRemove:
+   *   post:
+   *     tags: [Pages]
+   *     operationId: revertRemovePage
+   *     summary: Revert removed page
+   *     description: Restore a previously deleted (soft-deleted) page
+   *     requestBody:
+   *       required: true
+   *       content:
+   *         application/json:
+   *           schema:
+   *             type: object
+   *             required:
+   *               - page_id
+   *             properties:
+   *               page_id:
+   *                 type: string
+   *                 format: ObjectId
+   *                 description: Unique identifier of the page to restore
+   *                 example: "507f1f77bcf86cd799439011"
+   *               recursively:
+   *                 type: boolean
+   *                 description: Whether to restore child pages recursively
+   *                 default: false
+   *                 example: true
+   *           examples:
+   *             singleRevert:
+   *               summary: Revert single page
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *             recursiveRevert:
+   *               summary: Revert page and children
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 recursively: true
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageRevertSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       404:
+   *         $ref: '#/components/responses/NotFound'
+   *       409:
+   *         $ref: '#/components/responses/Conflict'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   api.revertRemove = async function(req, res, options) {
     const pageId = req.body.page_id;
@@ -406,12 +570,61 @@ module.exports = function(crowi, app) {
   };
 
   /**
-   * @api {post} /pages.unlink Remove the redirecting page
-   * @apiName UnlinkPage
-   * @apiGroup Page
+   * @swagger
+   *
+   * components:
+   *   schemas:
+   *     PageUnlinkData:
+   *       type: object
+   *       properties:
+   *         path:
+   *           type: string
+   *           description: Path for which redirects were removed
+   *           example: "/user/example"
+   *
+   *   responses:
+   *     PageUnlinkSuccess:
+   *       description: Successfully removed page redirects
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageUnlinkData'
    *
-   * @apiParam {String} page_id Page Id.
-   * @apiParam {String} revision_id
+   * /pages.unlink:
+   *   post:
+   *     tags: [Pages]
+   *     operationId: unlinkPage
+   *     summary: Remove page redirects
+   *     description: Remove all redirect entries that point to the specified page path
+   *     requestBody:
+   *       required: true
+   *       content:
+   *         application/json:
+   *           schema:
+   *             type: object
+   *             required:
+   *               - path
+   *             properties:
+   *               path:
+   *                 type: string
+   *                 description: Target path to remove redirects for
+   *                 example: "/user/example"
+   *           examples:
+   *             unlinkPage:
+   *               summary: Remove redirects to a page
+   *               value:
+   *                 path: "/user/example"
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageUnlinkSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   api.unlink = async function(req, res) {
     const path = req.body.path;

+ 18 - 16
apps/app/src/server/routes/search.ts

@@ -79,24 +79,26 @@ module.exports = function(crowi: Crowi, app) {
    *           content:
    *             application/json:
    *               schema:
-   *                 properties:
-   *                   ok:
-   *                     $ref: '#/components/schemas/V1ResponseOK'
-   *                   meta:
-   *                     $ref: '#/components/schemas/ElasticsearchResultMeta'
-   *                   totalCount:
-   *                     type: integer
-   *                     description: total count of pages
-   *                     example: 35
-   *                   data:
-   *                     type: array
-   *                     items:
-   *                       $ref: '#/components/schemas/Page'
-   *                     description: page list
+   *                 allOf:
+   *                   - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                   - type: object
+   *                     properties:
+   *                       meta:
+   *                         $ref: '#/components/schemas/ElasticsearchResultMeta'
+   *                         description: Elasticsearch metadata
+   *                       totalCount:
+   *                         type: integer
+   *                         description: total count of pages
+   *                         example: 35
+   *                       data:
+   *                         type: array
+   *                         items:
+   *                           $ref: '#/components/schemas/Page'
+   *                         description: page list
    *         403:
-   *           $ref: '#/components/responses/403'
+   *           $ref: '#/components/responses/Forbidden'
    *         500:
-   *           $ref: '#/components/responses/500'
+   *           $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /search search page

+ 29 - 23
apps/app/src/server/routes/tag.js

@@ -37,15 +37,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    tags:
-   *                      $ref: '#/components/schemas/Tags'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        tags:
+   *                          $ref: '#/components/schemas/Tags'
+   *                          description: List of matching tags
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /tags.search search tags
@@ -91,15 +93,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    tags:
-   *                      $ref: '#/components/schemas/Tags'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        tags:
+   *                          $ref: '#/components/schemas/Tags'
+   *                          description: Updated tags for the page
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /tags.update update tags on view-mode (not edit-mode)
@@ -168,17 +172,19 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    data:
-   *                      type: array
-   *                      items:
-   *                        $ref: '#/components/schemas/Tag'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        data:
+   *                          type: array
+   *                          items:
+   *                            $ref: '#/components/schemas/Tag'
+   *                          description: List of tags with count information
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /tags.list get tagnames and count pages relate each tag