Jelajahi Sumber

fix type error for routes

Yuki Takei 2 bulan lalu
induk
melakukan
6c2bedcdf0
26 mengubah file dengan 445 tambahan dan 407 penghapusan
  1. 14 9
      apps/app/src/features/openai/server/routes/ai-assistant.ts
  2. 11 6
      apps/app/src/features/openai/server/routes/ai-assistants.ts
  3. 11 8
      apps/app/src/features/openai/server/routes/delete-ai-assistant.ts
  4. 11 8
      apps/app/src/features/openai/server/routes/delete-thread.ts
  5. 17 17
      apps/app/src/features/openai/server/routes/edit/index.ts
  6. 19 9
      apps/app/src/features/openai/server/routes/get-recent-threads.ts
  7. 20 13
      apps/app/src/features/openai/server/routes/get-threads.ts
  8. 22 12
      apps/app/src/features/openai/server/routes/message/get-messages.ts
  9. 17 13
      apps/app/src/features/openai/server/routes/message/post-message.ts
  10. 4 3
      apps/app/src/features/openai/server/routes/middlewares/certify-ai-service.ts
  11. 7 9
      apps/app/src/features/openai/server/routes/set-default-ai-assistant.ts
  12. 17 9
      apps/app/src/features/openai/server/routes/thread.ts
  13. 11 8
      apps/app/src/features/openai/server/routes/update-ai-assistant.ts
  14. 6 9
      apps/app/src/server/routes/apiv3/page/check-page-existence.ts
  15. 3 5
      apps/app/src/server/routes/apiv3/page/create-page.ts
  16. 75 67
      apps/app/src/server/routes/apiv3/page/get-page-paths-with-descendant-count.ts
  17. 4 7
      apps/app/src/server/routes/apiv3/page/get-yjs-data.ts
  18. 4 9
      apps/app/src/server/routes/apiv3/page/publish-page.ts
  19. 47 50
      apps/app/src/server/routes/apiv3/page/sync-latest-revision-body-to-yjs-draft.ts
  20. 6 9
      apps/app/src/server/routes/apiv3/page/unpublish-page.ts
  21. 4 5
      apps/app/src/server/routes/apiv3/page/update-page.ts
  22. 33 34
      apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts
  23. 34 35
      apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts
  24. 41 42
      apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts
  25. 4 6
      apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts
  26. 3 5
      apps/app/src/server/routes/apiv3/user/get-related-groups.ts

+ 14 - 9
apps/app/src/features/openai/server/routes/ai-assistant.ts

@@ -1,3 +1,4 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
@@ -17,15 +18,13 @@ import { upsertAiAssistantValidator } from './middlewares/upsert-ai-assistant-va
 
 const logger = loggerFactory('growi:routes:apiv3:openai:create-ai-assistant');
 
-type CreateAssistantFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqBody = UpsertAiAssistantData;
 
-type Req = Request<undefined, Response, ReqBody> & {
-  user: IUserHasId;
+type Req = Request<Record<string, string>, ApiV3Response, ReqBody> & {
+  user?: IUserHasId;
 };
 
-export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => {
+export const createAiAssistantFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
   return [
@@ -34,20 +33,26 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => {
     }),
     loginRequiredStrictly,
     certifyAiService,
-    upsertAiAssistantValidator,
+    ...upsertAiAssistantValidator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const openaiService = getOpenaiService();
       if (openaiService == null) {
         return res.apiv3Err(new ErrorV3('GROWI AI is not enabled'), 501);
       }
 
       try {
-        const aiAssistantData = { ...req.body, owner: req.user._id };
+        const aiAssistantData = { ...req.body, owner: user._id };
 
         const isLearnablePageLimitExceeded =
           await openaiService.isLearnablePageLimitExceeded(
-            req.user,
+            user,
             aiAssistantData.pagePathPatterns,
           );
         if (isLearnablePageLimitExceeded) {
@@ -59,7 +64,7 @@ export const createAiAssistantFactory: CreateAssistantFactory = (crowi) => {
 
         const aiAssistant = await openaiService.createAiAssistant(
           req.body,
-          req.user,
+          user,
         );
 
         return res.apiv3({ aiAssistant });

+ 11 - 6
apps/app/src/features/openai/server/routes/ai-assistants.ts

@@ -1,3 +1,4 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
@@ -14,13 +15,11 @@ import { certifyAiService } from './middlewares/certify-ai-service';
 
 const logger = loggerFactory('growi:routes:apiv3:openai:get-ai-assistants');
 
-type GetAiAssistantsFactory = (crowi: Crowi) => RequestHandler[];
-
-type Req = Request<undefined, Response, undefined> & {
-  user: IUserHasId;
+type Req = Request<Record<string, string>, ApiV3Response, undefined> & {
+  user?: IUserHasId;
 };
 
-export const getAiAssistantsFactory: GetAiAssistantsFactory = (crowi) => {
+export const getAiAssistantsFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
   return [
@@ -30,6 +29,12 @@ export const getAiAssistantsFactory: GetAiAssistantsFactory = (crowi) => {
     loginRequiredStrictly,
     certifyAiService,
     async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const openaiService = getOpenaiService();
       if (openaiService == null) {
         return res.apiv3Err(new ErrorV3('GROWI AI is not enabled'), 501);
@@ -37,7 +42,7 @@ export const getAiAssistantsFactory: GetAiAssistantsFactory = (crowi) => {
 
       try {
         const accessibleAiAssistants =
-          await openaiService.getAccessibleAiAssistants(req.user);
+          await openaiService.getAccessibleAiAssistants(user);
 
         return res.apiv3({ accessibleAiAssistants });
       } catch (err) {

+ 11 - 8
apps/app/src/features/openai/server/routes/delete-ai-assistant.ts

@@ -1,8 +1,9 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import { param, type ValidationChain } from 'express-validator';
+import { param } from 'express-validator';
 import { isHttpError } from 'http-errors';
 
 import type Crowi from '~/server/crowi';
@@ -17,20 +18,18 @@ import { certifyAiService } from './middlewares/certify-ai-service';
 
 const logger = loggerFactory('growi:routes:apiv3:openai:delete-ai-assistants');
 
-type DeleteAiAssistantsFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqParams = {
   id: string;
 };
 
-type Req = Request<ReqParams, Response, undefined> & {
-  user: IUserHasId;
+type Req = Request<ReqParams, ApiV3Response, undefined> & {
+  user?: IUserHasId;
 };
 
-export const deleteAiAssistantsFactory: DeleteAiAssistantsFactory = (crowi) => {
+export const deleteAiAssistantsFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     param('id').isMongoId().withMessage('aiAssistant id is required'),
   ];
 
@@ -40,11 +39,15 @@ export const deleteAiAssistantsFactory: DeleteAiAssistantsFactory = (crowi) => {
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const { id } = req.params;
       const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
 
       try {
         const deletedAiAssistant = await deleteAiAssistant(user._id, id);

+ 11 - 8
apps/app/src/features/openai/server/routes/delete-thread.ts

@@ -1,8 +1,9 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import { param, type ValidationChain } from 'express-validator';
+import { param } from 'express-validator';
 import { isHttpError } from 'http-errors';
 
 import type Crowi from '~/server/crowi';
@@ -18,18 +19,16 @@ import { certifyAiService } from './middlewares/certify-ai-service';
 
 const logger = loggerFactory('growi:routes:apiv3:openai:delete-thread');
 
-type DeleteThreadFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqParams = IApiv3DeleteThreadParams;
 
-type Req = Request<ReqParams, Response, undefined> & {
-  user: IUserHasId;
+type Req = Request<ReqParams, ApiV3Response, undefined> & {
+  user?: IUserHasId;
 };
 
-export const deleteThreadFactory: DeleteThreadFactory = (crowi) => {
+export const deleteThreadFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     param('aiAssistantId').isMongoId().withMessage('threadId is required'),
     param('threadRelationId')
       .isMongoId()
@@ -42,11 +41,15 @@ export const deleteThreadFactory: DeleteThreadFactory = (crowi) => {
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const { aiAssistantId, threadRelationId } = req.params;
       const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
 
       const openaiService = getOpenaiService();
       if (openaiService == null) {

+ 17 - 17
apps/app/src/features/openai/server/routes/edit/index.ts

@@ -1,9 +1,9 @@
+import assert from 'node:assert';
 import { getIdStringForRef } from '@growi/core';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
-import type { Request, RequestHandler, Response } from 'express';
-import type { ValidationChain } from 'express-validator';
+import type { Request, RequestHandler } from 'express';
 import { body } from 'express-validator';
 import { zodResponseFormat } from 'openai/helpers/zod';
 import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
@@ -53,16 +53,10 @@ const LlmEditorAssistantResponseSchema = z
   })
   .describe('The response format for the editor assistant');
 
-type Req = Request<undefined, Response, EditRequestBody> & {
-  user: IUserHasId;
+type Req = Request<Record<string, string>, ApiV3Response, EditRequestBody> & {
+  user?: IUserHasId;
 };
 
-// -----------------------------------------------------------------------------
-// Endpoint handler factory
-// -----------------------------------------------------------------------------
-
-type PostMessageHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
 // -----------------------------------------------------------------------------
 // Instructions
 // -----------------------------------------------------------------------------
@@ -174,13 +168,13 @@ ${
 /**
  * Create endpoint handlers for editor assistant
  */
-export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (
-  crowi,
-) => {
+export const postMessageToEditHandlersFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
   // Validator setup
-  const validator: ValidationChain[] = [
+  const validator = [
     body('userMessage')
       .isString()
       .withMessage('userMessage must be string')
@@ -213,9 +207,15 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const {
         userMessage,
         pageBody,
@@ -260,7 +260,7 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (
       if (aiAssistantId != null) {
         const isAiAssistantUsable = await openaiService.isAiAssistantUsable(
           aiAssistantId,
-          req.user,
+          user,
         );
         if (!isAiAssistantUsable) {
           return res.apiv3Err(
@@ -338,7 +338,7 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (
 
           // Process annotations
           if (content?.type === 'text' && content?.text?.annotations != null) {
-            await replaceAnnotationWithPageLink(content, req.user.lang);
+            await replaceAnnotationWithPageLink(content, user.lang);
           }
 
           // Process text

+ 19 - 9
apps/app/src/features/openai/server/routes/get-recent-threads.ts

@@ -1,7 +1,8 @@
+import assert from 'node:assert';
 import { type IUserHasId, SCOPE } from '@growi/core';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import { query, type ValidationChain } from 'express-validator';
+import { query } from 'express-validator';
 import type { PaginateResult } from 'mongoose';
 
 import type Crowi from '~/server/crowi';
@@ -19,21 +20,24 @@ import { certifyAiService } from './middlewares/certify-ai-service';
 
 const logger = loggerFactory('growi:routes:apiv3:openai:get-recent-threads');
 
-type GetRecentThreadsFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqQuery = {
   page?: number;
   limit?: number;
 };
 
-type Req = Request<undefined, Response, undefined, ReqQuery> & {
-  user: IUserHasId;
+type Req = Request<
+  Record<string, string>,
+  ApiV3Response,
+  undefined,
+  ReqQuery
+> & {
+  user?: IUserHasId;
 };
 
-export const getRecentThreadsFactory: GetRecentThreadsFactory = (crowi) => {
+export const getRecentThreadsFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     query('page')
       .optional()
       .isInt()
@@ -52,9 +56,15 @@ export const getRecentThreadsFactory: GetRecentThreadsFactory = (crowi) => {
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const openaiService = getOpenaiService();
       if (openaiService == null) {
         return res.apiv3Err(new ErrorV3('GROWI AI is not enabled'), 501);
@@ -64,7 +74,7 @@ export const getRecentThreadsFactory: GetRecentThreadsFactory = (crowi) => {
         const paginateResult: PaginateResult<ThreadRelationDocument> =
           await ThreadRelationModel.paginate(
             {
-              userId: req.user._id,
+              userId: user._id,
               type: ThreadType.KNOWLEDGE,
               isActive: true,
             },

+ 20 - 13
apps/app/src/features/openai/server/routes/get-threads.ts

@@ -1,8 +1,9 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import { param, type ValidationChain } from 'express-validator';
+import { param } from 'express-validator';
 
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
@@ -16,34 +17,36 @@ import { certifyAiService } from './middlewares/certify-ai-service';
 
 const logger = loggerFactory('growi:routes:apiv3:openai:get-threads');
 
-type GetThreadsFactory = (crowi: Crowi) => RequestHandler[];
-
-type ReqParams = {
-  aiAssistantId: string;
-};
-
-type Req = Request<ReqParams, Response, undefined> & {
-  user: IUserHasId;
+type Req = Request<Record<string, string>, ApiV3Response, undefined> & {
+  user?: IUserHasId;
 };
 
-export const getThreadsFactory: GetThreadsFactory = (crowi) => {
+export const getThreadsFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     param('aiAssistantId')
       .isMongoId()
       .withMessage('aiAssistantId must be string'),
   ];
 
   return [
+    // biome-ignore lint/suspicious/noTsIgnore: Suppress auto fix by lefthook
+    // @ts-ignore - Scope type causes "Type instantiation is excessively deep" with tsgo
     accessTokenParser([SCOPE.READ.FEATURES.AI_ASSISTANT], {
       acceptLegacy: true,
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const openaiService = getOpenaiService();
       if (openaiService == null) {
         return res.apiv3Err(new ErrorV3('GROWI AI is not enabled'), 501);
@@ -51,10 +54,14 @@ export const getThreadsFactory: GetThreadsFactory = (crowi) => {
 
       try {
         const { aiAssistantId } = req.params;
+        assert(
+          aiAssistantId != null,
+          'aiAssistantId is required (validated by express-validator)',
+        );
 
         const isAiAssistantUsable = await openaiService.isAiAssistantUsable(
           aiAssistantId,
-          req.user,
+          user,
         );
         if (!isAiAssistantUsable) {
           return res.apiv3Err(

+ 22 - 12
apps/app/src/features/openai/server/routes/message/get-messages.ts

@@ -1,8 +1,9 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import { param, type ValidationChain } from 'express-validator';
+import { param } from 'express-validator';
 
 import type Crowi from '~/server/crowi';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
@@ -16,24 +17,22 @@ import { certifyAiService } from '../middlewares/certify-ai-service';
 
 const logger = loggerFactory('growi:routes:apiv3:openai:get-message');
 
-type GetMessagesFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqParam = {
-  threadId: string;
-  aiAssistantId: string;
+  threadId?: string;
+  aiAssistantId?: string;
   before?: string;
   after?: string;
   limit?: number;
 };
 
-type Req = Request<ReqParam, Response, undefined> & {
-  user: IUserHasId;
+type Req = Request<ReqParam, ApiV3Response, undefined> & {
+  user?: IUserHasId;
 };
 
-export const getMessagesFactory: GetMessagesFactory = (crowi) => {
+export const getMessagesFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     param('threadId').isString().withMessage('threadId must be string'),
     param('aiAssistantId')
       .isMongoId()
@@ -49,9 +48,15 @@ export const getMessagesFactory: GetMessagesFactory = (crowi) => {
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const openaiService = getOpenaiService();
       if (openaiService == null) {
         return res.apiv3Err(new ErrorV3('GROWI AI is not enabled'), 501);
@@ -60,9 +65,14 @@ export const getMessagesFactory: GetMessagesFactory = (crowi) => {
       try {
         const { threadId, aiAssistantId, limit, before, after } = req.params;
 
+        assert(
+          threadId != null && aiAssistantId != null,
+          'threadId and aiAssistantId are required (validated by express-validator)',
+        );
+
         const isAiAssistantUsable = await openaiService.isAiAssistantUsable(
           aiAssistantId,
-          req.user,
+          user,
         );
         if (!isAiAssistantUsable) {
           return res.apiv3Err(
@@ -73,7 +83,7 @@ export const getMessagesFactory: GetMessagesFactory = (crowi) => {
 
         const messages = await openaiService.getMessageData(
           threadId,
-          req.user.lang,
+          user.lang,
           {
             limit,
             before,

+ 17 - 13
apps/app/src/features/openai/server/routes/message/post-message.ts

@@ -1,8 +1,8 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
-import type { Request, RequestHandler, Response } from 'express';
-import type { ValidationChain } from 'express-validator';
+import type { Request, RequestHandler } from 'express';
 import { body } from 'express-validator';
 import type { AssistantStream } from 'openai/lib/AssistantStream';
 import type { MessageDelta } from 'openai/resources/beta/threads/messages.mjs';
@@ -56,18 +56,14 @@ type ReqBody = {
   extendedThinkingMode?: boolean;
 };
 
-type Req = Request<undefined, Response, ReqBody> & {
-  user: IUserHasId;
+type Req = Request<Record<string, string>, ApiV3Response, ReqBody> & {
+  user?: IUserHasId;
 };
 
-type PostMessageHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
-export const postMessageHandlersFactory: PostMessageHandlersFactory = (
-  crowi,
-) => {
+export const postMessageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     body('userMessage')
       .isString()
       .withMessage('userMessage must be string')
@@ -83,14 +79,22 @@ export const postMessageHandlersFactory: PostMessageHandlersFactory = (
   ];
 
   return [
+    // biome-ignore lint/suspicious/noTsIgnore: Suppress auto fix by lefthook
+    // @ts-ignore - Scope type causes "Type instantiation is excessively deep" with tsgo
     accessTokenParser([SCOPE.WRITE.FEATURES.AI_ASSISTANT], {
       acceptLegacy: true,
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const { aiAssistantId, threadId } = req.body;
 
       if (threadId == null) {
@@ -110,7 +114,7 @@ export const postMessageHandlersFactory: PostMessageHandlersFactory = (
 
       const isAiAssistantUsable = await openaiService.isAiAssistantUsable(
         aiAssistantId,
-        req.user,
+        user,
       );
       if (!isAiAssistantUsable) {
         return res.apiv3Err(
@@ -187,7 +191,7 @@ export const postMessageHandlersFactory: PostMessageHandlersFactory = (
 
         // If annotation is found
         if (content?.type === 'text' && content?.text?.annotations != null) {
-          await replaceAnnotationWithPageLink(content, req.user.lang);
+          await replaceAnnotationWithPageLink(content, user.lang);
         }
 
         res.write(`data: ${JSON.stringify(delta)}\n\n`);

+ 4 - 3
apps/app/src/features/openai/server/routes/middlewares/certify-ai-service.ts

@@ -1,5 +1,6 @@
-import type { NextFunction, Request, Response } from 'express';
+import type { NextFunction, Request } from 'express';
 
+import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 
@@ -8,8 +9,8 @@ import { OpenaiServiceTypes } from '../../../interfaces/ai';
 const logger = loggerFactory('growi:middlewares:certify-ai-service');
 
 export const certifyAiService = (
-  req: Request,
-  res: Response & { apiv3Err },
+  _req: Request,
+  res: ApiV3Response,
   next: NextFunction,
 ): void => {
   const aiEnabled = configManager.getConfig('app:aiEnabled');

+ 7 - 9
apps/app/src/features/openai/server/routes/set-default-ai-assistant.ts

@@ -1,7 +1,7 @@
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import { body, param, type ValidationChain } from 'express-validator';
+import { body, param } from 'express-validator';
 import { isHttpError } from 'http-errors';
 
 import type Crowi from '~/server/crowi';
@@ -20,8 +20,6 @@ const logger = loggerFactory(
   'growi:routes:apiv3:openai:set-default-ai-assistants',
 );
 
-type setDefaultAiAssistantFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqParams = {
   id: string;
 };
@@ -30,15 +28,15 @@ type ReqBody = {
   isDefault: boolean;
 };
 
-type Req = Request<ReqParams, Response, ReqBody>;
+type Req = Request<ReqParams, ApiV3Response, ReqBody>;
 
-export const setDefaultAiAssistantFactory: setDefaultAiAssistantFactory = (
-  crowi,
-) => {
+export const setDefaultAiAssistantFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
   const adminRequired = adminRequiredFactory(crowi);
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     param('id').isMongoId().withMessage('aiAssistant id is required'),
     body('isDefault').isBoolean().withMessage('isDefault is required'),
   ];
@@ -50,7 +48,7 @@ export const setDefaultAiAssistantFactory: setDefaultAiAssistantFactory = (
     loginRequiredStrictly,
     adminRequired,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const openaiService = getOpenaiService();

+ 17 - 9
apps/app/src/features/openai/server/routes/thread.ts

@@ -1,8 +1,8 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import type { ValidationChain } from 'express-validator';
 import { body } from 'express-validator';
 
 import type Crowi from '~/server/crowi';
@@ -24,16 +24,18 @@ type ReqBody = {
   initialUserMessage?: string;
 };
 
-type CreateThreadReq = Request<undefined, ApiV3Response, ReqBody> & {
-  user: IUserHasId;
+type CreateThreadReq = Request<
+  Record<string, string>,
+  ApiV3Response,
+  ReqBody
+> & {
+  user?: IUserHasId;
 };
 
-type CreateThreadFactory = (crowi: Crowi) => RequestHandler[];
-
-export const createThreadHandlersFactory: CreateThreadFactory = (crowi) => {
+export const createThreadHandlersFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     body('type')
       .isIn(Object.values(ThreadType))
       .withMessage('type must be one of "editor" or "knowledge"'),
@@ -53,9 +55,15 @@ export const createThreadHandlersFactory: CreateThreadFactory = (crowi) => {
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: CreateThreadReq, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
+
       const openaiService = getOpenaiService();
       if (openaiService == null) {
         return res.apiv3Err(new ErrorV3('GROWI AI is not enabled'), 501);
@@ -67,7 +75,7 @@ export const createThreadHandlersFactory: CreateThreadFactory = (crowi) => {
 
       try {
         const thread = await openaiService.createThread(
-          req.user._id,
+          user._id,
           type,
           aiAssistantId,
           initialUserMessage,

+ 11 - 8
apps/app/src/features/openai/server/routes/update-ai-assistant.ts

@@ -1,8 +1,9 @@
+import assert from 'node:assert';
 import type { IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import { param, type ValidationChain } from 'express-validator';
+import { param } from 'express-validator';
 import { isHttpError } from 'http-errors';
 
 import type Crowi from '~/server/crowi';
@@ -19,22 +20,20 @@ import { upsertAiAssistantValidator } from './middlewares/upsert-ai-assistant-va
 
 const logger = loggerFactory('growi:routes:apiv3:openai:update-ai-assistants');
 
-type UpdateAiAssistantsFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqParams = {
   id: string;
 };
 
 type ReqBody = UpsertAiAssistantData;
 
-type Req = Request<ReqParams, Response, ReqBody> & {
-  user: IUserHasId;
+type Req = Request<ReqParams, ApiV3Response, ReqBody> & {
+  user?: IUserHasId;
 };
 
-export const updateAiAssistantsFactory: UpdateAiAssistantsFactory = (crowi) => {
+export const updateAiAssistantsFactory = (crowi: Crowi): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-  const validator: ValidationChain[] = [
+  const validator = [
     param('id').isMongoId().withMessage('aiAssistant id is required'),
     ...upsertAiAssistantValidator,
   ];
@@ -45,11 +44,15 @@ export const updateAiAssistantsFactory: UpdateAiAssistantsFactory = (crowi) => {
     }),
     loginRequiredStrictly,
     certifyAiService,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const { id } = req.params;
       const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
 
       const openaiService = getOpenaiService();
       if (openaiService == null) {

+ 6 - 9
apps/app/src/server/routes/apiv3/page/check-page-existence.ts

@@ -3,7 +3,6 @@ import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { normalizePath } from '@growi/core/dist/utils/path-utils';
 import type { Request, RequestHandler } from 'express';
-import type { ValidationChain } from 'express-validator';
 import { query } from 'express-validator';
 import mongoose from 'mongoose';
 
@@ -23,27 +22,25 @@ type ReqQuery = {
 };
 
 interface Req extends Request<ReqQuery, ApiV3Response> {
-  user: IUserHasId;
+  user?: IUserHasId;
 }
 
-type CreatePageHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
-export const checkPageExistenceHandlersFactory: CreatePageHandlersFactory = (
-  crowi,
-) => {
+export const checkPageExistenceHandlersFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
   const Page = mongoose.model<IPage, PageModel>('Page');
 
   const loginRequired = loginRequiredFactory(crowi, true);
 
   // define validators for req.body
-  const validator: ValidationChain[] = [
+  const validator = [
     query('path').isString().withMessage('The param "path" must be specified'),
   ];
 
   return [
     accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }),
     loginRequired,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const { path } = req.query;

+ 3 - 5
apps/app/src/server/routes/apiv3/page/create-page.ts

@@ -11,7 +11,7 @@ import {
   attachTitleHeader,
   normalizePath,
 } from '@growi/core/dist/utils/path-utils';
-import type { Request, RequestHandler } from 'express';
+import type { Request } from 'express';
 import type { ValidationChain } from 'express-validator';
 import { body } from 'express-validator';
 import type { HydratedDocument } from 'mongoose';
@@ -113,9 +113,7 @@ interface CreatePageRequest extends Request<undefined, ApiV3Response, ReqBody> {
   user: IUserHasId;
 }
 
-type CreatePageHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
-export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
+export const createPageHandlersFactory = (crowi: Crowi) => {
   const Page = mongoose.model<IPage, PageModel>('Page');
   const User = mongoose.model<IUser, { isExistUserByUserPagePath: any }>(
     'User',
@@ -290,7 +288,7 @@ export const createPageHandlersFactory: CreatePageHandlersFactory = (crowi) => {
     loginRequiredStrictly,
     excludeReadOnlyUser,
     addActivity,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: CreatePageRequest, res: ApiV3Response) => {
       const { body: bodyByParam, pageTags: tagsByParam } = req.body;

+ 75 - 67
apps/app/src/server/routes/apiv3/page/get-page-paths-with-descendant-count.ts

@@ -1,3 +1,4 @@
+import assert from 'node:assert';
 import type { IPage, IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import type { Request, RequestHandler } from 'express';
@@ -16,83 +17,90 @@ import type { ApiV3Response } from '../interfaces/apiv3-response';
 
 const logger = loggerFactory('growi:routes:apiv3:page:get-pages-by-page-paths');
 
-type GetPagePathsWithDescendantCountFactory = (
-  crowi: Crowi,
-) => RequestHandler[];
-
 type ReqQuery = {
-  paths: string[];
+  paths?: string[];
   userGroups?: string[];
   isIncludeEmpty?: boolean;
   includeAnyoneWithTheLink?: boolean;
 };
 
-interface Req extends Request<undefined, ApiV3Response, undefined, ReqQuery> {
-  user: IUserHasId;
+interface Req
+  extends Request<Record<string, string>, ApiV3Response, undefined, ReqQuery> {
+  user?: IUserHasId;
 }
-export const getPagePathsWithDescendantCountFactory: GetPagePathsWithDescendantCountFactory =
-  (crowi) => {
-    const Page = mongoose.model<IPage, PageModel>('Page');
-    const loginRequiredStrictly = loginRequiredFactory(crowi);
+export const getPagePathsWithDescendantCountFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
+  const Page = mongoose.model<IPage, PageModel>('Page');
+  const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-    const validator: ValidationChain[] = [
-      query('paths').isArray().withMessage('paths must be an array of strings'),
-      query('paths').custom((paths: string[]) => {
-        if (paths.length > 300) {
-          throw new Error(
-            'paths must be an array of strings with a maximum length of 300',
-          );
-        }
-        return true;
-      }),
-      query('paths.*') // each item of paths
-        .isString()
-        .withMessage('paths must be an array of strings'),
+  const validator: ValidationChain[] = [
+    query('paths').isArray().withMessage('paths must be an array of strings'),
+    query('paths').custom((paths: string[]) => {
+      if (paths.length > 300) {
+        throw new Error(
+          'paths must be an array of strings with a maximum length of 300',
+        );
+      }
+      return true;
+    }),
+    query('paths.*') // each item of paths
+      .isString()
+      .withMessage('paths must be an array of strings'),
 
-      query('userGroups')
-        .optional()
-        .isArray()
-        .withMessage('userGroups must be an array of strings'),
-      query('userGroups.*') // each item of userGroups
-        .isMongoId()
-        .withMessage('userGroups must be an array of strings'),
+    query('userGroups')
+      .optional()
+      .isArray()
+      .withMessage('userGroups must be an array of strings'),
+    query('userGroups.*') // each item of userGroups
+      .isMongoId()
+      .withMessage('userGroups must be an array of strings'),
 
-      query('isIncludeEmpty')
-        .optional()
-        .isBoolean()
-        .withMessage('isIncludeEmpty must be a boolean'),
-      query('isIncludeEmpty').toBoolean(),
+    query('isIncludeEmpty')
+      .optional()
+      .isBoolean()
+      .withMessage('isIncludeEmpty must be a boolean'),
+    query('isIncludeEmpty').toBoolean(),
 
-      query('includeAnyoneWithTheLink')
-        .optional()
-        .isBoolean()
-        .withMessage('includeAnyoneWithTheLink must be a boolean'),
-      query('includeAnyoneWithTheLink').toBoolean(),
-    ];
+    query('includeAnyoneWithTheLink')
+      .optional()
+      .isBoolean()
+      .withMessage('includeAnyoneWithTheLink must be a boolean'),
+    query('includeAnyoneWithTheLink').toBoolean(),
+  ];
 
-    return [
-      accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }),
-      loginRequiredStrictly,
-      validator,
-      apiV3FormValidator,
-      async (req: Req, res: ApiV3Response) => {
-        const { paths, userGroups, isIncludeEmpty, includeAnyoneWithTheLink } =
-          req.query;
+  return [
+    accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }),
+    loginRequiredStrictly,
+    ...validator,
+    apiV3FormValidator,
+    async (req: Req, res: ApiV3Response) => {
+      const { user } = req;
+      assert(
+        user != null,
+        'user is required (ensured by loginRequiredStrictly middleware)',
+      );
 
-        try {
-          const pagePathsWithDescendantCount =
-            await Page.descendantCountByPaths(
-              paths,
-              req.user,
-              userGroups,
-              isIncludeEmpty,
-              includeAnyoneWithTheLink,
-            );
-          return res.apiv3({ pagePathsWithDescendantCount });
-        } catch (err) {
-          logger.error(err);
-          return res.apiv3Err(err);
-        }
-      },
-    ];
-  };
+      const { paths, userGroups, isIncludeEmpty, includeAnyoneWithTheLink } =
+        req.query;
+      assert(
+        paths != null,
+        'paths is required (validated by express-validator)',
+      );
+
+      try {
+        const pagePathsWithDescendantCount = await Page.descendantCountByPaths(
+          paths,
+          user,
+          userGroups,
+          isIncludeEmpty,
+          includeAnyoneWithTheLink,
+        );
+        return res.apiv3({ pagePathsWithDescendantCount });
+      } catch (err) {
+        logger.error(err);
+        return res.apiv3Err(err);
+      }
+    },
+  ];
+};

+ 4 - 7
apps/app/src/server/routes/apiv3/page/get-yjs-data.ts

@@ -2,7 +2,6 @@ import type { IPage, IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import type { ValidationChain } from 'express-validator';
 import { param } from 'express-validator';
 import mongoose from 'mongoose';
 
@@ -17,20 +16,18 @@ import type { ApiV3Response } from '../interfaces/apiv3-response';
 
 const logger = loggerFactory('growi:routes:apiv3:page:get-yjs-data');
 
-type GetYjsDataHandlerFactory = (crowi: Crowi) => RequestHandler[];
-
 type ReqParams = {
   pageId: string;
 };
 interface Req extends Request<ReqParams, ApiV3Response> {
-  user: IUserHasId;
+  user?: IUserHasId;
 }
-export const getYjsDataHandlerFactory: GetYjsDataHandlerFactory = (crowi) => {
+export const getYjsDataHandlerFactory = (crowi: Crowi): RequestHandler[] => {
   const Page = mongoose.model<IPage, PageModel>('Page');
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
   // define validators for req.params
-  const validator: ValidationChain[] = [
+  const validator = [
     param('pageId')
       .isMongoId()
       .withMessage('The param "pageId" must be specified'),
@@ -39,7 +36,7 @@ export const getYjsDataHandlerFactory: GetYjsDataHandlerFactory = (crowi) => {
   return [
     accessTokenParser([SCOPE.READ.FEATURES.PAGE], { acceptLegacy: true }),
     loginRequiredStrictly,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const { pageId } = req.params;

+ 4 - 9
apps/app/src/server/routes/apiv3/page/publish-page.ts

@@ -2,7 +2,6 @@ import type { IPage, IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import type { ValidationChain } from 'express-validator';
 import { param } from 'express-validator';
 import mongoose from 'mongoose';
 
@@ -22,20 +21,16 @@ type ReqParams = {
 };
 
 interface Req extends Request<ReqParams, ApiV3Response> {
-  user: IUserHasId;
+  user?: IUserHasId;
 }
 
-type PublishPageHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
-export const publishPageHandlersFactory: PublishPageHandlersFactory = (
-  crowi,
-) => {
+export const publishPageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
   const Page = mongoose.model<IPage, PageModel>('Page');
 
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
   // define validators for req.body
-  const validator: ValidationChain[] = [
+  const validator = [
     param('pageId')
       .isMongoId()
       .withMessage('The param "pageId" must be specified'),
@@ -44,7 +39,7 @@ export const publishPageHandlersFactory: PublishPageHandlersFactory = (
   return [
     accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }),
     loginRequiredStrictly,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const { pageId } = req.params;

+ 47 - 50
apps/app/src/server/routes/apiv3/page/sync-latest-revision-body-to-yjs-draft.ts

@@ -20,10 +20,6 @@ const logger = loggerFactory(
   'growi:routes:apiv3:page:sync-latest-revision-body-to-yjs-draft',
 );
 
-type SyncLatestRevisionBodyToYjsDraftHandlerFactory = (
-  crowi: Crowi,
-) => RequestHandler[];
-
 type ReqParams = {
   pageId: string;
 };
@@ -33,53 +29,54 @@ type ReqBody = {
 interface Req extends Request<ReqParams, ApiV3Response, ReqBody> {
   user: IUserHasId;
 }
-export const syncLatestRevisionBodyToYjsDraftHandlerFactory: SyncLatestRevisionBodyToYjsDraftHandlerFactory =
-  (crowi) => {
-    const Page = mongoose.model<IPage, PageModel>('Page');
-    const loginRequiredStrictly = loginRequiredFactory(crowi);
+export const syncLatestRevisionBodyToYjsDraftHandlerFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
+  const Page = mongoose.model<IPage, PageModel>('Page');
+  const loginRequiredStrictly = loginRequiredFactory(crowi);
 
-    // define validators for req.params
-    const validator: ValidationChain[] = [
-      param('pageId')
-        .isMongoId()
-        .withMessage('The param "pageId" must be specified'),
-      body('editingMarkdownLength')
-        .optional()
-        .isInt()
-        .withMessage('The body "editingMarkdownLength" must be integer'),
-    ];
+  // define validators for req.params
+  const validator: ValidationChain[] = [
+    param('pageId')
+      .isMongoId()
+      .withMessage('The param "pageId" must be specified'),
+    body('editingMarkdownLength')
+      .optional()
+      .isInt()
+      .withMessage('The body "editingMarkdownLength" must be integer'),
+  ];
 
-    return [
-      accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }),
-      loginRequiredStrictly,
-      validator,
-      apiV3FormValidator,
-      async (req: Req, res: ApiV3Response) => {
-        const { pageId } = req.params;
-        const { editingMarkdownLength } = req.body;
+  return [
+    accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }),
+    loginRequiredStrictly,
+    ...validator,
+    apiV3FormValidator,
+    async (req: Req, res: ApiV3Response) => {
+      const { pageId } = req.params;
+      const { editingMarkdownLength } = req.body;
 
-        // check whether accessible
-        if (!(await Page.isAccessiblePageByViewer(pageId, req.user))) {
-          return res.apiv3Err(
-            new ErrorV3(
-              'Current user is not accessible to this page.',
-              'forbidden-page',
-            ),
-            403,
-          );
-        }
+      // check whether accessible
+      if (!(await Page.isAccessiblePageByViewer(pageId, req.user))) {
+        return res.apiv3Err(
+          new ErrorV3(
+            'Current user is not accessible to this page.',
+            'forbidden-page',
+          ),
+          403,
+        );
+      }
 
-        try {
-          const yjsService = getYjsService();
-          const result = await yjsService.syncWithTheLatestRevisionForce(
-            pageId,
-            editingMarkdownLength,
-          );
-          return res.apiv3(result);
-        } catch (err) {
-          logger.error(err);
-          return res.apiv3Err(err);
-        }
-      },
-    ];
-  };
+      try {
+        const yjsService = getYjsService();
+        const result = await yjsService.syncWithTheLatestRevisionForce(
+          pageId,
+          editingMarkdownLength,
+        );
+        return res.apiv3(result);
+      } catch (err) {
+        logger.error(err);
+        return res.apiv3Err(err);
+      }
+    },
+  ];
+};

+ 6 - 9
apps/app/src/server/routes/apiv3/page/unpublish-page.ts

@@ -2,7 +2,6 @@ import type { IPage, IUserHasId } from '@growi/core';
 import { SCOPE } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request, RequestHandler } from 'express';
-import type { ValidationChain } from 'express-validator';
 import { param } from 'express-validator';
 import mongoose from 'mongoose';
 
@@ -22,20 +21,18 @@ type ReqParams = {
 };
 
 interface Req extends Request<ReqParams, ApiV3Response> {
-  user: IUserHasId;
+  user?: IUserHasId;
 }
 
-type UnpublishPageHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
-export const unpublishPageHandlersFactory: UnpublishPageHandlersFactory = (
-  crowi,
-) => {
+export const unpublishPageHandlersFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
   const Page = mongoose.model<IPage, PageModel>('Page');
 
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
   // define validators for req.body
-  const validator: ValidationChain[] = [
+  const validator = [
     param('pageId')
       .isMongoId()
       .withMessage('The param "pageId" must be specified'),
@@ -44,7 +41,7 @@ export const unpublishPageHandlersFactory: UnpublishPageHandlersFactory = (
   return [
     accessTokenParser([SCOPE.WRITE.FEATURES.PAGE], { acceptLegacy: true }),
     loginRequiredStrictly,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: Req, res: ApiV3Response) => {
       const { pageId } = req.params;

+ 4 - 5
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -44,13 +44,12 @@ const logger = loggerFactory('growi:routes:apiv3:page:update-page');
 
 type ReqBody = IApiv3PageUpdateParams;
 
-interface UpdatePageRequest extends Request<undefined, ApiV3Response, ReqBody> {
+interface UpdatePageRequest
+  extends Request<Record<string, string>, ApiV3Response, ReqBody> {
   user: IUserHasId;
 }
 
-type UpdatePageHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
-export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
+export const updatePageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
   const Page = mongoose.model<IPage, PageModel>('Page');
   const Revision = mongoose.model<IRevisionHasId>('Revision');
 
@@ -190,7 +189,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
     loginRequiredStrictly,
     excludeReadOnlyUser,
     addActivity,
-    validator,
+    ...validator,
     apiV3FormValidator,
     async (req: UpdatePageRequest, res: ApiV3Response) => {
       const { pageId, revisionId, body, origin, grant } = req.body;

+ 33 - 34
apps/app/src/server/routes/apiv3/personal-setting/delete-access-token.ts

@@ -24,14 +24,12 @@ type ReqQuery = {
 };
 
 type DeleteAccessTokenRequest = Request<
-  undefined,
+  Record<string, string>,
   ApiV3Response,
   undefined,
   ReqQuery
 >;
 
-type DeleteAccessTokenHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
 const validator = [
   query('tokenId')
     .exists()
@@ -40,38 +38,39 @@ const validator = [
     .withMessage('tokenId must be a string'),
 ];
 
-export const deleteAccessTokenHandlersFactory: DeleteAccessTokenHandlersFactory =
-  (crowi) => {
-    const loginRequiredStrictly = loginRequiredFactory(crowi);
-    const addActivity = generateAddActivityMiddleware();
-    const activityEvent = crowi.events.activity;
+export const deleteAccessTokenHandlersFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
+  const loginRequiredStrictly = loginRequiredFactory(crowi);
+  const addActivity = generateAddActivityMiddleware();
+  const activityEvent = crowi.events.activity;
 
-    return [
-      accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
-      loginRequiredStrictly,
-      excludeReadOnlyUser,
-      addActivity,
-      validator,
-      apiV3FormValidator,
-      async (req: DeleteAccessTokenRequest, res: ApiV3Response) => {
-        const { query } = req;
-        const { tokenId } = query;
+  return [
+    accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
+    loginRequiredStrictly,
+    excludeReadOnlyUser,
+    addActivity,
+    ...validator,
+    apiV3FormValidator,
+    async (req: DeleteAccessTokenRequest, res: ApiV3Response) => {
+      const { query } = req;
+      const { tokenId } = query;
 
-        try {
-          await AccessToken.deleteTokenById(tokenId);
+      try {
+        await AccessToken.deleteTokenById(tokenId);
 
-          const parameters = {
-            action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE,
-          };
-          activityEvent.emit('update', res.locals.activity._id, parameters);
+        const parameters = {
+          action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE,
+        };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
 
-          return res.apiv3({});
-        } catch (err) {
-          logger.error(err);
-          return res.apiv3Err(
-            new ErrorV3(err.toString(), 'delete-access-token-failed'),
-          );
-        }
-      },
-    ];
-  };
+        return res.apiv3({});
+      } catch (err) {
+        logger.error(err);
+        return res.apiv3Err(
+          new ErrorV3(err.toString(), 'delete-access-token-failed'),
+        );
+      }
+    },
+  ];
+};

+ 34 - 35
apps/app/src/server/routes/apiv3/personal-setting/delete-all-access-tokens.ts

@@ -19,41 +19,40 @@ const logger = loggerFactory(
 );
 
 interface DeleteAllAccessTokensRequest
-  extends Request<undefined, ApiV3Response, undefined> {
+  extends Request<Record<string, string>, ApiV3Response, undefined> {
   user: IUserHasId;
 }
 
-type DeleteAllAccessTokensHandlersFactory = (crowi: Crowi) => RequestHandler[];
-
-export const deleteAllAccessTokensHandlersFactory: DeleteAllAccessTokensHandlersFactory =
-  (crowi) => {
-    const loginRequiredStrictly = loginRequiredFactory(crowi);
-    const addActivity = generateAddActivityMiddleware();
-    const activityEvent = crowi.events.activity;
-
-    return [
-      accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
-      loginRequiredStrictly,
-      excludeReadOnlyUser,
-      addActivity,
-      async (req: DeleteAllAccessTokensRequest, res: ApiV3Response) => {
-        const { user } = req;
-
-        try {
-          await AccessToken.deleteAllTokensByUserId(user._id);
-
-          const parameters = {
-            action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE,
-          };
-          activityEvent.emit('update', res.locals.activity._id, parameters);
-
-          return res.apiv3({});
-        } catch (err) {
-          logger.error(err);
-          return res.apiv3Err(
-            new ErrorV3(err.toString(), 'delete-all-access-token-failed'),
-          );
-        }
-      },
-    ];
-  };
+export const deleteAllAccessTokensHandlersFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
+  const loginRequiredStrictly = loginRequiredFactory(crowi);
+  const addActivity = generateAddActivityMiddleware();
+  const activityEvent = crowi.events.activity;
+
+  return [
+    accessTokenParser([SCOPE.WRITE.USER_SETTINGS.API.ACCESS_TOKEN]),
+    loginRequiredStrictly,
+    excludeReadOnlyUser,
+    addActivity,
+    async (req: DeleteAllAccessTokensRequest, res: ApiV3Response) => {
+      const { user } = req;
+
+      try {
+        await AccessToken.deleteAllTokensByUserId(user._id);
+
+        const parameters = {
+          action: SupportedAction.ACTION_USER_ACCESS_TOKEN_DELETE,
+        };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+
+        return res.apiv3({});
+      } catch (err) {
+        logger.error(err);
+        return res.apiv3Err(
+          new ErrorV3(err.toString(), 'delete-all-access-token-failed'),
+        );
+      }
+    },
+  ];
+};

+ 41 - 42
apps/app/src/server/routes/apiv3/personal-setting/generate-access-token.ts

@@ -26,12 +26,10 @@ type ReqBody = {
 };
 
 interface GenerateAccessTokenRequest
-  extends Request<undefined, ApiV3Response, ReqBody> {
+  extends Request<Record<string, string>, ApiV3Response, ReqBody> {
   user: IUserHasId;
 }
 
-type GenerateAccessTokenHandlerFactory = (crowi: Crowi) => RequestHandler[];
-
 const validator = [
   body('expiredAt')
     .exists()
@@ -75,42 +73,43 @@ const validator = [
     .withMessage('Invalid scope'),
 ];
 
-export const generateAccessTokenHandlerFactory: GenerateAccessTokenHandlerFactory =
-  (crowi) => {
-    const loginRequiredStrictly = loginRequiredFactory(crowi);
-    const activityEvent = crowi.events.activity;
-    const addActivity = generateAddActivityMiddleware();
-
-    return [
-      loginRequiredStrictly,
-      excludeReadOnlyUser,
-      addActivity,
-      validator,
-      apiV3FormValidator,
-      async (req: GenerateAccessTokenRequest, res: ApiV3Response) => {
-        const { user, body } = req;
-        const { expiredAt, description, scopes } = body;
-
-        try {
-          const tokenData = await AccessToken.generateToken(
-            user._id,
-            expiredAt,
-            scopes,
-            description,
-          );
-
-          const parameters = {
-            action: SupportedAction.ACTION_USER_ACCESS_TOKEN_CREATE,
-          };
-          activityEvent.emit('update', res.locals.activity._id, parameters);
-
-          return res.apiv3(tokenData);
-        } catch (err) {
-          logger.error(err);
-          return res.apiv3Err(
-            new ErrorV3(err.toString(), 'generate-access-token-failed'),
-          );
-        }
-      },
-    ];
-  };
+export const generateAccessTokenHandlerFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
+  const loginRequiredStrictly = loginRequiredFactory(crowi);
+  const activityEvent = crowi.events.activity;
+  const addActivity = generateAddActivityMiddleware();
+
+  return [
+    loginRequiredStrictly,
+    excludeReadOnlyUser,
+    addActivity,
+    ...validator,
+    apiV3FormValidator,
+    async (req: GenerateAccessTokenRequest, res: ApiV3Response) => {
+      const { user, body } = req;
+      const { expiredAt, description, scopes } = body;
+
+      try {
+        const tokenData = await AccessToken.generateToken(
+          user._id,
+          expiredAt,
+          scopes,
+          description,
+        );
+
+        const parameters = {
+          action: SupportedAction.ACTION_USER_ACCESS_TOKEN_CREATE,
+        };
+        activityEvent.emit('update', res.locals.activity._id, parameters);
+
+        return res.apiv3(tokenData);
+      } catch (err) {
+        logger.error(err);
+        return res.apiv3Err(
+          new ErrorV3(err.toString(), 'generate-access-token-failed'),
+        );
+      }
+    },
+  ];
+};

+ 4 - 6
apps/app/src/server/routes/apiv3/personal-setting/get-access-tokens.ts

@@ -18,15 +18,13 @@ const logger = loggerFactory(
 );
 
 interface GetAccessTokenRequest
-  extends Request<undefined, ApiV3Response, undefined> {
+  extends Request<Record<string, string>, ApiV3Response, undefined> {
   user: IUserHasId;
 }
 
-type GetAccessTokenHandlerFactory = (crowi: Crowi) => RequestHandler[];
-
-export const getAccessTokenHandlerFactory: GetAccessTokenHandlerFactory = (
-  crowi,
-) => {
+export const getAccessTokenHandlerFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
   const addActivity = generateAddActivityMiddleware();
 

+ 3 - 5
apps/app/src/server/routes/apiv3/user/get-related-groups.ts

@@ -12,15 +12,13 @@ import type { ApiV3Response } from '../interfaces/apiv3-response';
 
 const logger = loggerFactory('growi:routes:apiv3:user:get-related-groups');
 
-type GetRelatedGroupsHandlerFactory = (crowi: Crowi) => RequestHandler[];
-
 interface Req extends Request {
   user: IUserHasId;
 }
 
-export const getRelatedGroupsHandlerFactory: GetRelatedGroupsHandlerFactory = (
-  crowi,
-) => {
+export const getRelatedGroupsHandlerFactory = (
+  crowi: Crowi,
+): RequestHandler[] => {
   const loginRequiredStrictly = loginRequiredFactory(crowi);
 
   return [