feat(server): add search docs by keyword gql api (#12866)

close AI-220









#### PR Dependency Tree


* **PR #12866** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new document search capability, allowing users to search
for documents by keyword within a workspace.
- Search results include document details such as title, highlights,
creation and update timestamps, and creator/updater information.
  - Added support for limiting the number of search results returned.
- **Tests**
- Added comprehensive end-to-end and snapshot tests to ensure accuracy
and access control for the new search functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
fengmk2
2025-06-20 19:29:37 +08:00
committed by GitHub
parent 10e981aa6d
commit 011f92f7da
9 changed files with 430 additions and 0 deletions

View File

@@ -10,6 +10,8 @@ import { IndexerService, SearchNodeWithMeta } from './service';
import {
AggregateInput,
AggregateResultObjectType,
SearchDocObjectType,
SearchDocsInput,
SearchInput,
SearchQueryOccur,
SearchQueryType,
@@ -86,6 +88,29 @@ export class IndexerResolver {
};
}
@ResolveField(() => [SearchDocObjectType], {
description: 'Search docs by keyword',
})
async searchDocs(
@CurrentUser() me: UserType,
@Parent() workspace: WorkspaceType,
@Args('input') input: SearchDocsInput
): Promise<SearchDocObjectType[]> {
const docs = await this.indexer.searchDocsByKeyword(
workspace.id,
input.keyword,
{
limit: input.limit,
}
);
const needs = await this.ac
.user(me.id)
.workspace(workspace.id)
.docs(docs, 'Doc.Read');
return needs;
}
#addWorkspaceFilter(
workspace: WorkspaceType,
input: SearchInput | AggregateInput

View File

@@ -9,6 +9,7 @@ import {
} from '@nestjs/graphql';
import { GraphQLJSONObject } from 'graphql-scalars';
import { PublicUserType } from '../../core/user';
import { PublicUser } from '../../models';
import { SearchTable } from './tables';
@@ -171,6 +172,18 @@ export class AggregateInput {
options!: AggregateOptions;
}
@InputType()
export class SearchDocsInput {
@Field(() => String)
keyword!: string;
@Field({
nullable: true,
description: 'Limit the number of docs to return, default is 20',
})
limit?: number;
}
@ObjectType()
export class BlockObjectType {
@Field(() => [String], { nullable: true })
@@ -320,3 +333,30 @@ export class AggregateResultObjectType {
@Field(() => SearchResultPagination)
pagination!: SearchResultPagination;
}
@ObjectType()
export class SearchDocObjectType implements Partial<SearchDoc> {
@Field(() => String)
docId!: string;
@Field(() => String)
title!: string;
@Field(() => String)
blockId!: string;
@Field(() => String)
highlight!: string;
@Field(() => Date)
createdAt!: Date;
@Field(() => Date)
updatedAt!: Date;
@Field(() => PublicUserType, { nullable: true })
createdByUser?: PublicUserType;
@Field(() => PublicUserType, { nullable: true })
updatedByUser?: PublicUserType;
}