Kaynağa Gözat

add /page-listing/item endpoint

Yuki Takei 5 ay önce
ebeveyn
işleme
184f4ebffb

+ 16 - 14
apps/app/src/features/opentelemetry/server/anonymization/handlers/page-listing-api-handler.spec.ts

@@ -12,20 +12,22 @@ describe('pageListingApiModule', () => {
 
   describe('canHandle', () => {
     it.each`
-      description                           | url                                                  | expected
-      ${'ancestors-children endpoint'}      | ${'/_api/v3/page-listing/ancestors-children?path=/'} | ${true}
-      ${'children endpoint'}                | ${'/_api/v3/page-listing/children?path=/docs'}       | ${true}
-      ${'info endpoint'}                    | ${'/_api/v3/page-listing/info?path=/wiki'}           | ${true}
-      ${'ancestors-children without query'} | ${'/_api/v3/page-listing/ancestors-children'}        | ${true}
-      ${'children without query'}           | ${'/_api/v3/page-listing/children'}                  | ${true}
-      ${'info without query'}               | ${'/_api/v3/page-listing/info'}                      | ${true}
-      ${'other page-listing endpoint'}      | ${'/_api/v3/page-listing/other'}                     | ${false}
-      ${'different API version'}            | ${'/_api/v2/page-listing/children'}                  | ${false}
-      ${'non-page-listing API'}             | ${'/_api/v3/pages/list'}                             | ${false}
-      ${'regular page path'}                | ${'/page/path'}                                      | ${false}
-      ${'root path'}                        | ${'/'}                                               | ${false}
-      ${'empty URL'}                        | ${''}                                                | ${false}
-      ${'partial match'}                    | ${'/_api/v3/page-listing-other/children'}            | ${false}
+      description                           | url                                                         | expected
+      ${'ancestors-children endpoint'}      | ${'/_api/v3/page-listing/ancestors-children?path=/'}        | ${true}
+      ${'children endpoint'}                | ${'/_api/v3/page-listing/children?path=/docs'}              | ${true}
+      ${'info endpoint'}                    | ${'/_api/v3/page-listing/info?path=/wiki'}                  | ${true}
+      ${'item endpoint'}                    | ${'/_api/v3/page-listing/item?id=68b686d3984fce462ecc7c05'} | ${true}
+      ${'ancestors-children without query'} | ${'/_api/v3/page-listing/ancestors-children'}               | ${true}
+      ${'children without query'}           | ${'/_api/v3/page-listing/children'}                         | ${true}
+      ${'info without query'}               | ${'/_api/v3/page-listing/info'}                             | ${true}
+      ${'item without query'}               | ${'/_api/v3/page-listing/item'}                             | ${true}
+      ${'other page-listing endpoint'}      | ${'/_api/v3/page-listing/other'}                            | ${false}
+      ${'different API version'}            | ${'/_api/v2/page-listing/children'}                         | ${false}
+      ${'non-page-listing API'}             | ${'/_api/v3/pages/list'}                                    | ${false}
+      ${'regular page path'}                | ${'/page/path'}                                             | ${false}
+      ${'root path'}                        | ${'/'}                                                      | ${false}
+      ${'empty URL'}                        | ${''}                                                       | ${false}
+      ${'partial match'}                    | ${'/_api/v3/page-listing-other/children'}                   | ${false}
     `('should return $expected for $description: $url', ({ url, expected }) => {
       const result = pageListingApiModule.canHandle(url);
       expect(result).toBe(expected);

+ 2 - 0
apps/app/src/features/opentelemetry/server/anonymization/handlers/page-listing-api-handler.ts

@@ -20,6 +20,7 @@ export const pageListingApiModule: AnonymizationModule = {
     return (
       url.includes('/_api/v3/page-listing/ancestors-children') ||
       url.includes('/_api/v3/page-listing/children') ||
+      url.includes('/_api/v3/page-listing/item') ||
       url.includes('/_api/v3/page-listing/info')
     );
     // Add other page-listing endpoints here as needed
@@ -39,6 +40,7 @@ export const pageListingApiModule: AnonymizationModule = {
     if (
       url.includes('/_api/v3/page-listing/ancestors-children') ||
       url.includes('/_api/v3/page-listing/children') ||
+      url.includes('/_api/v3/page-listing/item') ||
       url.includes('/_api/v3/page-listing/info')
     ) {
       const anonymizedUrl = anonymizeQueryParams(url, ['path']);

+ 1 - 8
apps/app/src/interfaces/page.ts

@@ -23,14 +23,7 @@ export type IPageForItem = Partial<
 
 export type IPageForTreeItem = Pick<
   IPageHasId,
-  | '_id'
-  | 'path'
-  | 'parent'
-  | 'descendantCount'
-  | 'revision'
-  | 'grant'
-  | 'isEmpty'
-  | 'wip'
+  '_id' | 'path' | 'descendantCount' | 'grant' | 'isEmpty' | 'wip'
 > & {
   processData?: IPageOperationProcessData;
 };

+ 62 - 0
apps/app/src/server/routes/apiv3/page-listing.ts

@@ -158,6 +158,68 @@ const routerFactory = (crowi: Crowi): Router => {
       }
     });
 
+  /**
+   * @swagger
+   *
+   * /page-listing/item:
+   *   get:
+   *     tags: [PageListing]
+   *     security:
+   *       - bearer: []
+   *       - accessTokenInQuery: []
+   *     summary: /page-listing/item
+   *     description: Get a single page item for tree display
+   *     parameters:
+   *       - name: id
+   *         in: query
+   *         required: true
+   *         schema:
+   *           type: string
+   *     responses:
+   *       200:
+   *         description: Page item data
+   *         content:
+   *           application/json:
+   *             schema:
+   *               type: object
+   *               properties:
+   *                 item:
+   *                   $ref: '#/components/schemas/PageForTreeItem'
+   */
+  router.get('/item',
+    accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }),
+    loginRequired, validator.pageIdOrPathRequired, apiV3FormValidator, async(req: AuthorizedRequest, res: ApiV3Response) => {
+      const { id } = req.query;
+
+      if (id == null) {
+        return res.apiv3Err(new ErrorV3('id parameter is required'));
+      }
+
+      try {
+        const Page = mongoose.model<HydratedDocument<PageDocument>, PageModel>('Page');
+        const page = await Page.findByIdAndViewer(id as string, req.user, null, true);
+
+        if (page == null) {
+          return res.apiv3Err(new ErrorV3('Page not found'), 404);
+        }
+
+        const item: IPageForTreeItem = {
+          _id: page._id.toString(),
+          path: page.path,
+          descendantCount: page.descendantCount,
+          grant: page.grant,
+          isEmpty: page.isEmpty,
+          wip: page.wip ?? false,
+        };
+
+        return res.apiv3({ item });
+      }
+      catch (err) {
+        logger.error('Error occurred while fetching page item.', err);
+        return res.apiv3Err(new ErrorV3('Error occurred while fetching page item.'));
+      }
+    });
+
   /**
    * @swagger
    *