ソースを参照

refactor(suggest-path): migrate to features/ directory pattern

Move all suggest-path code from server/routes/apiv3/ai-tools/ to
features/suggest-path/ following the project's feature-based architecture
convention. No business logic changes — pure structural refactoring.

- interfaces/: shared type definitions
- server/services/: AI pipeline, search, grant resolution (7 modules)
- server/routes/apiv3/: route handler with middleware chain
- server/integration-tests/: full pipeline integration tests
- ai-tools/index.ts retained as aggregation router

Key decisions documented in design.md:
- No barrel exports (follows features/openai/ convention)
- Aggregation router preserved for future ai-tools features
- R4 category suggestion migrated as-is (under review status unchanged)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VANELLOPE\tomoyuki-t 1 ヶ月 前
コミット
41515c6dcd
20 ファイル変更96 行追加46 行削除
  1. 43 2
      .kiro/specs/suggest-path/design.md
  2. 0 0
      apps/app/src/features/suggest-path/interfaces/suggest-path-types.ts
  3. 6 6
      apps/app/src/features/suggest-path/server/integration-tests/suggest-path-integration.spec.ts
  4. 16 12
      apps/app/src/features/suggest-path/server/routes/apiv3/index.spec.ts
  5. 8 8
      apps/app/src/features/suggest-path/server/routes/apiv3/index.ts
  6. 1 1
      apps/app/src/features/suggest-path/server/services/analyze-content.spec.ts
  7. 4 1
      apps/app/src/features/suggest-path/server/services/analyze-content.ts
  8. 2 2
      apps/app/src/features/suggest-path/server/services/evaluate-candidates.spec.ts
  9. 1 1
      apps/app/src/features/suggest-path/server/services/evaluate-candidates.ts
  10. 0 0
      apps/app/src/features/suggest-path/server/services/generate-category-suggestion.spec.ts
  11. 5 2
      apps/app/src/features/suggest-path/server/services/generate-category-suggestion.ts
  12. 0 0
      apps/app/src/features/suggest-path/server/services/generate-memo-suggestion.spec.ts
  13. 2 2
      apps/app/src/features/suggest-path/server/services/generate-memo-suggestion.ts
  14. 1 1
      apps/app/src/features/suggest-path/server/services/generate-suggestions.spec.ts
  15. 4 4
      apps/app/src/features/suggest-path/server/services/generate-suggestions.ts
  16. 0 0
      apps/app/src/features/suggest-path/server/services/resolve-parent-grant.spec.ts
  17. 0 0
      apps/app/src/features/suggest-path/server/services/resolve-parent-grant.ts
  18. 1 1
      apps/app/src/features/suggest-path/server/services/retrieve-search-candidates.spec.ts
  19. 1 1
      apps/app/src/features/suggest-path/server/services/retrieve-search-candidates.ts
  20. 1 2
      apps/app/src/server/routes/apiv3/ai-tools/index.ts

+ 43 - 2
.kiro/specs/suggest-path/design.md

@@ -104,6 +104,46 @@ graph TB
 - **New components**: ContentAnalyzer and CandidateEvaluator wrap GROWI AI calls with suggest-path-specific prompting
 - **New components**: ContentAnalyzer and CandidateEvaluator wrap GROWI AI calls with suggest-path-specific prompting
 - **Steering compliance**: Feature-based separation, named exports, TypeScript strict typing
 - **Steering compliance**: Feature-based separation, named exports, TypeScript strict typing
 
 
+### Code Organization (Directory Structure)
+
+All suggest-path code resides in `features/suggest-path/` following the project's feature-based architecture pattern (reference: `features/openai/`).
+
+**Target structure**:
+
+```text
+apps/app/src/features/suggest-path/
+├── interfaces/
+│   └── suggest-path-types.ts           # Shared types (PathSuggestion, ContentAnalysis, etc.)
+├── server/
+│   ├── routes/
+│   │   └── apiv3/
+│   │       └── index.ts                # Router factory, handler + middleware chain
+│   ├── services/
+│   │   ├── generate-suggestions.ts     # Orchestrator
+│   │   ├── generate-suggestions.spec.ts
+│   │   ├── generate-memo-suggestion.ts
+│   │   ├── generate-memo-suggestion.spec.ts
+│   │   ├── analyze-content.ts          # AI call #1
+│   │   ├── analyze-content.spec.ts
+│   │   ├── evaluate-candidates.ts      # AI call #2
+│   │   ├── evaluate-candidates.spec.ts
+│   │   ├── retrieve-search-candidates.ts
+│   │   ├── retrieve-search-candidates.spec.ts
+│   │   ├── generate-category-suggestion.ts
+│   │   ├── generate-category-suggestion.spec.ts
+│   │   ├── resolve-parent-grant.ts
+│   │   └── resolve-parent-grant.spec.ts
+│   └── integration-tests/
+│       └── suggest-path-integration.spec.ts
+```
+
+**Key decisions**:
+
+- **No barrel export**: Consumers import directly from subpaths (following `features/openai/` convention)
+- **Aggregation router retained**: The `ai-tools` router remains in `server/routes/apiv3/` as an aggregation point, importing the suggest-path route factory from `~/features/suggest-path/server/routes/apiv3`. This allows future ai-tools features to register under the same namespace
+- **R4 (CategorySuggestionGenerator)**: Migrated as-is. The "under review" status is a requirements-level decision deferred to post-implementation reviewer discussion; the migration does not change its behavior
+- **`generate-search-suggestion.ts`**: Superseded by the `retrieveSearchCandidates` → `evaluateCandidates` pipeline and already removed from the codebase. Not part of the migration
+
 ### Implementation Paradigm
 ### Implementation Paradigm
 
 
 **Default**: All components are implemented as pure functions with immutable data. No classes unless explicitly justified.
 **Default**: All components are implemented as pure functions with immutable data. No classes unless explicitly justified.
@@ -307,9 +347,10 @@ sequenceDiagram
 
 
 **Implementation Notes**
 **Implementation Notes**
 
 
-- Route registered in `apps/app/src/server/routes/apiv3/index.js` as `router.use('/ai-tools', ...)`
+- Route handler lives in `features/suggest-path/server/routes/apiv3/index.ts`
+- Aggregation router in `apps/app/src/server/routes/apiv3/ai-tools/index.ts` imports the feature's route factory and mounts it: `router.use('/suggest-path', suggestPathRouteFactory(crowi))`
+- Aggregation router registered in `apps/app/src/server/routes/apiv3/index.js` as `router.use('/ai-tools', ...)`
 - Middleware chain: `accessTokenParser` → `loginRequiredStrictly` → `certifyAiService` → validators → `apiV3FormValidator` → handler
 - Middleware chain: `accessTokenParser` → `loginRequiredStrictly` → `certifyAiService` → validators → `apiV3FormValidator` → handler
-- Namespace `ai-tools` is tentative pending yuki confirmation; change requires single line edit in `index.js`
 
 
 ### Service Layer
 ### Service Layer
 
 

+ 0 - 0
apps/app/src/server/routes/apiv3/ai-tools/suggest-path-types.ts → apps/app/src/features/suggest-path/interfaces/suggest-path-types.ts


+ 6 - 6
apps/app/src/server/routes/apiv3/ai-tools/suggest-path-integration.spec.ts → apps/app/src/features/suggest-path/server/integration-tests/suggest-path-integration.spec.ts

@@ -103,7 +103,7 @@ vi.mock(
 );
 );
 
 
 // Mock analyzeContent — configurable per test via testState
 // Mock analyzeContent — configurable per test via testState
-vi.mock('./analyze-content', () => ({
+vi.mock('../services/analyze-content', () => ({
   analyzeContent: vi.fn().mockImplementation(() => {
   analyzeContent: vi.fn().mockImplementation(() => {
     if (testState.contentAnalysisError != null) {
     if (testState.contentAnalysisError != null) {
       return Promise.reject(testState.contentAnalysisError);
       return Promise.reject(testState.contentAnalysisError);
@@ -116,7 +116,7 @@ vi.mock('./analyze-content', () => ({
 }));
 }));
 
 
 // Mock retrieveSearchCandidates — configurable per test via testState
 // Mock retrieveSearchCandidates — configurable per test via testState
-vi.mock('./retrieve-search-candidates', () => ({
+vi.mock('../services/retrieve-search-candidates', () => ({
   retrieveSearchCandidates: vi.fn().mockImplementation(() => {
   retrieveSearchCandidates: vi.fn().mockImplementation(() => {
     if (testState.searchCandidatesError != null) {
     if (testState.searchCandidatesError != null) {
       return Promise.reject(testState.searchCandidatesError);
       return Promise.reject(testState.searchCandidatesError);
@@ -126,7 +126,7 @@ vi.mock('./retrieve-search-candidates', () => ({
 }));
 }));
 
 
 // Mock evaluateCandidates — configurable per test via testState
 // Mock evaluateCandidates — configurable per test via testState
-vi.mock('./evaluate-candidates', () => ({
+vi.mock('../services/evaluate-candidates', () => ({
   evaluateCandidates: vi.fn().mockImplementation(() => {
   evaluateCandidates: vi.fn().mockImplementation(() => {
     if (testState.evaluateCandidatesError != null) {
     if (testState.evaluateCandidatesError != null) {
       return Promise.reject(testState.evaluateCandidatesError);
       return Promise.reject(testState.evaluateCandidatesError);
@@ -136,7 +136,7 @@ vi.mock('./evaluate-candidates', () => ({
 }));
 }));
 
 
 // Mock generateCategorySuggestion — configurable per test via testState
 // Mock generateCategorySuggestion — configurable per test via testState
-vi.mock('./generate-category-suggestion', () => ({
+vi.mock('../services/generate-category-suggestion', () => ({
   generateCategorySuggestion: vi.fn().mockImplementation(() => {
   generateCategorySuggestion: vi.fn().mockImplementation(() => {
     if (testState.categorySuggestionError != null) {
     if (testState.categorySuggestionError != null) {
       return Promise.reject(testState.categorySuggestionError);
       return Promise.reject(testState.categorySuggestionError);
@@ -146,7 +146,7 @@ vi.mock('./generate-category-suggestion', () => ({
 }));
 }));
 
 
 // Mock resolveParentGrant — returns configurable grant value via testState
 // Mock resolveParentGrant — returns configurable grant value via testState
-vi.mock('./resolve-parent-grant', () => ({
+vi.mock('../services/resolve-parent-grant', () => ({
   resolveParentGrant: vi.fn().mockImplementation(() => {
   resolveParentGrant: vi.fn().mockImplementation(() => {
     return Promise.resolve(testState.parentGrant);
     return Promise.resolve(testState.parentGrant);
   }),
   }),
@@ -187,7 +187,7 @@ describe('POST /suggest-path integration', () => {
     });
     });
 
 
     // Import and mount the handler factory with real middleware chain
     // Import and mount the handler factory with real middleware chain
-    const { suggestPathHandlersFactory } = await import('./suggest-path');
+    const { suggestPathHandlersFactory } = await import('../routes/apiv3');
     const mockCrowi = {
     const mockCrowi = {
       searchService: { searchKeyword: vi.fn() },
       searchService: { searchKeyword: vi.fn() },
     } as unknown as Crowi;
     } as unknown as Crowi;

+ 16 - 12
apps/app/src/server/routes/apiv3/ai-tools/suggest-path.spec.ts → apps/app/src/features/suggest-path/server/routes/apiv3/index.spec.ts

@@ -14,20 +14,24 @@ const mocks = vi.hoisted(() => {
   };
   };
 });
 });
 
 
-vi.mock('./generate-suggestions', () => ({
+vi.mock('../../services/generate-suggestions', () => ({
   generateSuggestions: mocks.generateSuggestionsMock,
   generateSuggestions: mocks.generateSuggestionsMock,
 }));
 }));
 
 
 // Mock modules imported by the handler for dependency injection
 // Mock modules imported by the handler for dependency injection
-vi.mock('./analyze-content', () => ({ analyzeContent: vi.fn() }));
-vi.mock('./evaluate-candidates', () => ({ evaluateCandidates: vi.fn() }));
-vi.mock('./generate-category-suggestion', () => ({
+vi.mock('../../services/analyze-content', () => ({ analyzeContent: vi.fn() }));
+vi.mock('../../services/evaluate-candidates', () => ({
+  evaluateCandidates: vi.fn(),
+}));
+vi.mock('../../services/generate-category-suggestion', () => ({
   generateCategorySuggestion: vi.fn(),
   generateCategorySuggestion: vi.fn(),
 }));
 }));
-vi.mock('./retrieve-search-candidates', () => ({
+vi.mock('../../services/retrieve-search-candidates', () => ({
   retrieveSearchCandidates: vi.fn(),
   retrieveSearchCandidates: vi.fn(),
 }));
 }));
-vi.mock('./resolve-parent-grant', () => ({ resolveParentGrant: vi.fn() }));
+vi.mock('../../services/resolve-parent-grant', () => ({
+  resolveParentGrant: vi.fn(),
+}));
 
 
 vi.mock('~/server/middlewares/login-required', () => ({
 vi.mock('~/server/middlewares/login-required', () => ({
   default: mocks.loginRequiredFactoryMock,
   default: mocks.loginRequiredFactoryMock,
@@ -78,14 +82,14 @@ describe('suggestPathHandlersFactory', () => {
 
 
   describe('middleware chain', () => {
   describe('middleware chain', () => {
     it('should return an array of request handlers', async () => {
     it('should return an array of request handlers', async () => {
-      const { suggestPathHandlersFactory } = await import('./suggest-path');
+      const { suggestPathHandlersFactory } = await import('.');
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handlers = suggestPathHandlersFactory(mockCrowi);
       expect(Array.isArray(handlers)).toBe(true);
       expect(Array.isArray(handlers)).toBe(true);
       expect(handlers.length).toBeGreaterThanOrEqual(5);
       expect(handlers.length).toBeGreaterThanOrEqual(5);
     });
     });
 
 
     it('should include certifyAiService in the middleware chain', async () => {
     it('should include certifyAiService in the middleware chain', async () => {
-      const { suggestPathHandlersFactory } = await import('./suggest-path');
+      const { suggestPathHandlersFactory } = await import('.');
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handlers = suggestPathHandlersFactory(mockCrowi);
       expect(handlers).toContain(mocks.certifyAiServiceMock);
       expect(handlers).toContain(mocks.certifyAiServiceMock);
     });
     });
@@ -118,7 +122,7 @@ describe('suggestPathHandlersFactory', () => {
       ];
       ];
       mocks.generateSuggestionsMock.mockResolvedValue(suggestions);
       mocks.generateSuggestionsMock.mockResolvedValue(suggestions);
 
 
-      const { suggestPathHandlersFactory } = await import('./suggest-path');
+      const { suggestPathHandlersFactory } = await import('.');
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handler = handlers[handlers.length - 1] as RequestHandler;
       const handler = handlers[handlers.length - 1] as RequestHandler;
 
 
@@ -151,7 +155,7 @@ describe('suggestPathHandlersFactory', () => {
       ];
       ];
       mocks.generateSuggestionsMock.mockResolvedValue(suggestions);
       mocks.generateSuggestionsMock.mockResolvedValue(suggestions);
 
 
-      const { suggestPathHandlersFactory } = await import('./suggest-path');
+      const { suggestPathHandlersFactory } = await import('.');
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handler = handlers[handlers.length - 1] as RequestHandler;
       const handler = handlers[handlers.length - 1] as RequestHandler;
 
 
@@ -166,7 +170,7 @@ describe('suggestPathHandlersFactory', () => {
         new Error('Unexpected error'),
         new Error('Unexpected error'),
       );
       );
 
 
-      const { suggestPathHandlersFactory } = await import('./suggest-path');
+      const { suggestPathHandlersFactory } = await import('.');
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handler = handlers[handlers.length - 1] as RequestHandler;
       const handler = handlers[handlers.length - 1] as RequestHandler;
 
 
@@ -185,7 +189,7 @@ describe('suggestPathHandlersFactory', () => {
       mocks.findAllExternalUserGroupIdsMock.mockResolvedValue(['eg1']);
       mocks.findAllExternalUserGroupIdsMock.mockResolvedValue(['eg1']);
       mocks.generateSuggestionsMock.mockResolvedValue([]);
       mocks.generateSuggestionsMock.mockResolvedValue([]);
 
 
-      const { suggestPathHandlersFactory } = await import('./suggest-path');
+      const { suggestPathHandlersFactory } = await import('.');
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handlers = suggestPathHandlersFactory(mockCrowi);
       const handler = handlers[handlers.length - 1] as RequestHandler;
       const handler = handlers[handlers.length - 1] as RequestHandler;
 
 

+ 8 - 8
apps/app/src/server/routes/apiv3/ai-tools/suggest-path.ts → apps/app/src/features/suggest-path/server/routes/apiv3/index.ts

@@ -15,15 +15,15 @@ import UserGroupRelation from '~/server/models/user-group-relation';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { analyzeContent } from './analyze-content';
-import { evaluateCandidates } from './evaluate-candidates';
-import { generateCategorySuggestion } from './generate-category-suggestion';
-import { generateSuggestions } from './generate-suggestions';
-import { resolveParentGrant } from './resolve-parent-grant';
-import { retrieveSearchCandidates } from './retrieve-search-candidates';
-import type { SearchService } from './suggest-path-types';
+import type { SearchService } from '../../../interfaces/suggest-path-types';
+import { analyzeContent } from '../../services/analyze-content';
+import { evaluateCandidates } from '../../services/evaluate-candidates';
+import { generateCategorySuggestion } from '../../services/generate-category-suggestion';
+import { generateSuggestions } from '../../services/generate-suggestions';
+import { resolveParentGrant } from '../../services/resolve-parent-grant';
+import { retrieveSearchCandidates } from '../../services/retrieve-search-candidates';
 
 
-const logger = loggerFactory('growi:routes:apiv3:ai-tools:suggest-path');
+const logger = loggerFactory('growi:features:suggest-path:routes');
 
 
 type ReqBody = {
 type ReqBody = {
   body: string;
   body: string;

+ 1 - 1
apps/app/src/server/routes/apiv3/ai-tools/analyze-content.spec.ts → apps/app/src/features/suggest-path/server/services/analyze-content.spec.ts

@@ -1,5 +1,5 @@
+import type { ContentAnalysis } from '../../interfaces/suggest-path-types';
 import { analyzeContent } from './analyze-content';
 import { analyzeContent } from './analyze-content';
-import type { ContentAnalysis } from './suggest-path-types';
 
 
 const mocks = vi.hoisted(() => {
 const mocks = vi.hoisted(() => {
   return {
   return {

+ 4 - 1
apps/app/src/server/routes/apiv3/ai-tools/analyze-content.ts → apps/app/src/features/suggest-path/server/services/analyze-content.ts

@@ -6,7 +6,10 @@ import {
 } from '~/features/openai/server/services/client-delegator';
 } from '~/features/openai/server/services/client-delegator';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 
 
-import type { ContentAnalysis, InformationType } from './suggest-path-types';
+import type {
+  ContentAnalysis,
+  InformationType,
+} from '../../interfaces/suggest-path-types';
 
 
 const VALID_INFORMATION_TYPES: readonly InformationType[] = ['flow', 'stock'];
 const VALID_INFORMATION_TYPES: readonly InformationType[] = ['flow', 'stock'];
 
 

+ 2 - 2
apps/app/src/server/routes/apiv3/ai-tools/evaluate-candidates.spec.ts → apps/app/src/features/suggest-path/server/services/evaluate-candidates.spec.ts

@@ -1,9 +1,9 @@
-import { evaluateCandidates } from './evaluate-candidates';
 import type {
 import type {
   ContentAnalysis,
   ContentAnalysis,
   EvaluatedSuggestion,
   EvaluatedSuggestion,
   SearchCandidate,
   SearchCandidate,
-} from './suggest-path-types';
+} from '../../interfaces/suggest-path-types';
+import { evaluateCandidates } from './evaluate-candidates';
 
 
 const mocks = vi.hoisted(() => {
 const mocks = vi.hoisted(() => {
   return {
   return {

+ 1 - 1
apps/app/src/server/routes/apiv3/ai-tools/evaluate-candidates.ts → apps/app/src/features/suggest-path/server/services/evaluate-candidates.ts

@@ -10,7 +10,7 @@ import type {
   ContentAnalysis,
   ContentAnalysis,
   EvaluatedSuggestion,
   EvaluatedSuggestion,
   SearchCandidate,
   SearchCandidate,
-} from './suggest-path-types';
+} from '../../interfaces/suggest-path-types';
 
 
 const SYSTEM_PROMPT = [
 const SYSTEM_PROMPT = [
   'You are a page save location evaluator for a wiki system. ',
   'You are a page save location evaluator for a wiki system. ',

+ 0 - 0
apps/app/src/server/routes/apiv3/ai-tools/generate-category-suggestion.spec.ts → apps/app/src/features/suggest-path/server/services/generate-category-suggestion.spec.ts


+ 5 - 2
apps/app/src/server/routes/apiv3/ai-tools/generate-category-suggestion.ts → apps/app/src/features/suggest-path/server/services/generate-category-suggestion.ts

@@ -1,8 +1,11 @@
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 
 
+import type {
+  PathSuggestion,
+  SearchService,
+} from '../../interfaces/suggest-path-types';
+import { SuggestionType } from '../../interfaces/suggest-path-types';
 import { resolveParentGrant } from './resolve-parent-grant';
 import { resolveParentGrant } from './resolve-parent-grant';
-import type { PathSuggestion, SearchService } from './suggest-path-types';
-import { SuggestionType } from './suggest-path-types';
 
 
 const CATEGORY_LABEL = 'Save under category';
 const CATEGORY_LABEL = 'Save under category';
 const SEARCH_RESULT_LIMIT = 10;
 const SEARCH_RESULT_LIMIT = 10;

+ 0 - 0
apps/app/src/server/routes/apiv3/ai-tools/generate-memo-suggestion.spec.ts → apps/app/src/features/suggest-path/server/services/generate-memo-suggestion.spec.ts


+ 2 - 2
apps/app/src/server/routes/apiv3/ai-tools/generate-memo-suggestion.ts → apps/app/src/features/suggest-path/server/services/generate-memo-suggestion.ts

@@ -3,9 +3,9 @@ import { userHomepagePath } from '@growi/core/dist/utils/page-path-utils';
 
 
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
 
 
+import type { PathSuggestion } from '../../interfaces/suggest-path-types';
+import { SuggestionType } from '../../interfaces/suggest-path-types';
 import { resolveParentGrant } from './resolve-parent-grant';
 import { resolveParentGrant } from './resolve-parent-grant';
-import type { PathSuggestion } from './suggest-path-types';
-import { SuggestionType } from './suggest-path-types';
 
 
 const MEMO_LABEL = 'Save as memo';
 const MEMO_LABEL = 'Save as memo';
 const MEMO_DESCRIPTION = 'Save to your personal memo area';
 const MEMO_DESCRIPTION = 'Save to your personal memo area';

+ 1 - 1
apps/app/src/server/routes/apiv3/ai-tools/generate-suggestions.spec.ts → apps/app/src/features/suggest-path/server/services/generate-suggestions.spec.ts

@@ -5,7 +5,7 @@ import type {
   EvaluatedSuggestion,
   EvaluatedSuggestion,
   PathSuggestion,
   PathSuggestion,
   SearchCandidate,
   SearchCandidate,
-} from './suggest-path-types';
+} from '../../interfaces/suggest-path-types';
 
 
 const mocks = vi.hoisted(() => {
 const mocks = vi.hoisted(() => {
   return {
   return {

+ 4 - 4
apps/app/src/server/routes/apiv3/ai-tools/generate-suggestions.ts → apps/app/src/features/suggest-path/server/services/generate-suggestions.ts

@@ -2,17 +2,17 @@ import type { IUserHasId } from '@growi/core/dist/interfaces';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { generateMemoSuggestion } from './generate-memo-suggestion';
 import type {
 import type {
   ContentAnalysis,
   ContentAnalysis,
   EvaluatedSuggestion,
   EvaluatedSuggestion,
   PathSuggestion,
   PathSuggestion,
   SearchCandidate,
   SearchCandidate,
-} from './suggest-path-types';
-import { SuggestionType } from './suggest-path-types';
+} from '../../interfaces/suggest-path-types';
+import { SuggestionType } from '../../interfaces/suggest-path-types';
+import { generateMemoSuggestion } from './generate-memo-suggestion';
 
 
 const logger = loggerFactory(
 const logger = loggerFactory(
-  'growi:routes:apiv3:ai-tools:generate-suggestions',
+  'growi:features:suggest-path:generate-suggestions',
 );
 );
 
 
 export type GenerateSuggestionsDeps = {
 export type GenerateSuggestionsDeps = {

+ 0 - 0
apps/app/src/server/routes/apiv3/ai-tools/resolve-parent-grant.spec.ts → apps/app/src/features/suggest-path/server/services/resolve-parent-grant.spec.ts


+ 0 - 0
apps/app/src/server/routes/apiv3/ai-tools/resolve-parent-grant.ts → apps/app/src/features/suggest-path/server/services/resolve-parent-grant.ts


+ 1 - 1
apps/app/src/server/routes/apiv3/ai-tools/retrieve-search-candidates.spec.ts → apps/app/src/features/suggest-path/server/services/retrieve-search-candidates.spec.ts

@@ -1,7 +1,7 @@
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 import type { IUserHasId } from '@growi/core/dist/interfaces';
 
 
+import type { SearchCandidate } from '../../interfaces/suggest-path-types';
 import { retrieveSearchCandidates } from './retrieve-search-candidates';
 import { retrieveSearchCandidates } from './retrieve-search-candidates';
-import type { SearchCandidate } from './suggest-path-types';
 
 
 type HighlightData = Record<string, string[]>;
 type HighlightData = Record<string, string[]>;
 
 

+ 1 - 1
apps/app/src/server/routes/apiv3/ai-tools/retrieve-search-candidates.ts → apps/app/src/features/suggest-path/server/services/retrieve-search-candidates.ts

@@ -4,7 +4,7 @@ import type {
   SearchCandidate,
   SearchCandidate,
   SearchResultItem,
   SearchResultItem,
   SearchService,
   SearchService,
-} from './suggest-path-types';
+} from '../../interfaces/suggest-path-types';
 
 
 const DEFAULT_SCORE_THRESHOLD = 5.0;
 const DEFAULT_SCORE_THRESHOLD = 5.0;
 const SEARCH_RESULT_LIMIT = 20;
 const SEARCH_RESULT_LIMIT = 20;

+ 1 - 2
apps/app/src/server/routes/apiv3/ai-tools/index.ts

@@ -1,9 +1,8 @@
 import express from 'express';
 import express from 'express';
 
 
+import { suggestPathHandlersFactory } from '~/features/suggest-path/server/routes/apiv3';
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 
 
-import { suggestPathHandlersFactory } from './suggest-path';
-
 export const factory = (crowi: Crowi): express.Router => {
 export const factory = (crowi: Crowi): express.Router => {
   const router = express.Router();
   const router = express.Router();
   router.post('/suggest-path', suggestPathHandlersFactory(crowi));
   router.post('/suggest-path', suggestPathHandlersFactory(crowi));